chore(comments): includes 头注释英文化(R11 收尾)

把 includes 下 64 个 .vp 的文件头注释(含 traceback.vp 第 13-427 行的整块文件级说明)以及 2 处漏网的正文中文注释(ast/lexer.vp、json/__writer.vp)与 1 处中文 docstring(memhub.vp)逐行译为英文;同步把 fltk/gen_ffi.py 的生成模板与其自身注释英文化,使重新生成仍为英文。

只改注释/文档串文字:行数与代码逐字节不变(骨架比对 140 文件漂移 0),运行时中文字符串字面量 305 处保持原样。验证:三重重编 SUCCESS(170.21s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0。
This commit is contained in:
TermiNexus committed 2026-10-06 09:42:25 +08:00
1 parent 14ba84db8e
commit c0d56b127d
65 files changed
+744 -744

No files matched your search

+17 -17
View File
@@ -11,25 +11,25 @@ from .tokens import (
# ============================================================
# 整数字面量溢出(词法层 fail-fast)
# Integer literal overflow (lexer-layer fail-fast)
#
# 整数常量在 token/AST 中是 int64(tokens.vp `int_val` / exprs.vp `int_val`)。
# 旧实现用 `string.atoll`(string.vp:292)在 int64 下 `num = num * 10 + digit`
# 逐位累加 ⇒ 值 > 2^64-1 时**静默回绕**(如 2^64 回绕成 0,2^64-1 回绕成 -1)。
# 本实现在 `_emit_number` 里对**整数**字面量先做纯文本幅值判定
# (`_lex_int_exceeds_u64`,与 AssignGenMixin._int_text_exceeds_64 同口径:按
# 进制统计有效位数 / 与 2^64-1 的十进制文本做长度+字典序比较,**不落进任何定宽
# 容器、不回绕后再比较**),超出 2^64-1 即记录 fail-fast(不再静默回绕)。
# 合法范围 0..2^64-1 仍按 64 位位模式存入 int64(既有合法路径的产物形态不变,
# 如 0xFFFFFFFFFFFFFFFF → int64 -1、0xA5A5A5A5A5A5A5A5 保持其 64 位位模式)。
# 各进制(2/8/10/16)口径与 TPC(Python int(x, 0),任意精度)一致:接受区间同为
# [-2^63, 2^64-1](字面量本身无符号,负数侧由一元减表达),超出即拒绝。
# Integer constants are int64 in the token/AST (tokens.vp `int_val` / exprs.vp `int_val`).
# The old implementation used `string.atoll` (string.vp:292) to, under int64, do `num = num * 10 + digit`
# digit-by-digit accumulation => when the value > 2^64-1 it **silently wraps** (e.g. 2^64 wraps to 0, 2^64-1 wraps to -1).
# This implementation, in `_emit_number`, first performs a pure-text magnitude check on **integer** literals
# (`_lex_int_exceeds_u64`, using the same criteria as AssignGenMixin._int_text_exceeds_64: by
# radix, counting significant digits / comparing length + lexicographic order against the decimal text of 2^64-1, **without landing in any fixed-width
# container and without wrapping before the comparison**), so exceeding 2^64-1 records a fail-fast (no more silent wrapping).
# The legal range 0..2^64-1 is still stored into int64 as a 64-bit bit pattern (the shape of the results on the existing legal path is unchanged,
# e.g. 0xFFFFFFFFFFFFFFFF -> int64 -1, 0xA5A5A5A5A5A5A5A5 keeps its 64-bit bit pattern).
# The criteria for each radix (2/8/10/16) match TPC's (Python int(x, 0), arbitrary precision): the accepted interval is likewise
# [-2^63, 2^64-1] (literals themselves are unsigned; the negative side is expressed by unary minus), and anything beyond is rejected.
#
# 上报通道:lexer 无独立 fatal 出口,故记录到模块级三元组(`_lex_record_overflow`),
# 由 parser 的 parse_tokens 取出后转写 _g_parse_err_*(位置 + 消息),复用既有
# Traceback/SyntaxError 报告路径("文件:行:列 + 源码行"由该路径补全并终止编译)。
# 这覆盖全部源码字面量(文件级解析必经 parse_tokens);parse_expression 只用于编译器
# 合成的短类型串,不承载源码字面量,故不另设通道。
# Reporting channel: the lexer has no separate fatal exit, so it records into a module-level triple (`_lex_record_overflow`),
# which the parser's parse_tokens reads and transcribes into _g_parse_err_* (position + message), reusing the existing
# Traceback/SyntaxError report path (the "file:line:col + source line" is filled in by that path, which also terminates compilation).
# This covers all source literals (file-level parsing necessarily goes through parse_tokens); parse_expression is used only for compiler-
# synthesized short type strings and carries no source literals, so no separate channel is provided.
# ============================================================
_g_lex_err_line: int = 0
_g_lex_err_col: int = 0
+10 -10
View File
@@ -1,7 +1,7 @@
# ============================================================
# fltk — Viper 的 FLTK GUI 绑定包(对接 cfltk C API)
# fltk — Viper FLTK GUI binding package (wraps the cfltk C API)
#
# 用户用法:
# Usage:
# import fltk
# win = fltk.Fl_Window(400, 300, "Hello")
# box = fltk.Fl_Box(10, 10, 380, 40, "Hello, World!")
@@ -10,18 +10,18 @@
# win.show()
# fltk.Fl.run()
#
# 说明:
# * 本包只做 extern FFI(fltk._cfl* 模块)+ 少量 Pythonic 包装类;
# 所有 FLTK 对象都是不透明指针,方法即 cfltk 的 Fl_<类>_<动作>。
# * 完整 C API 通过 `from fltk._cfl import *` 等直接可用。
# * 回调支持:Viper 模块级函数可直接作为 Fl_Callback 传给 FLTK——
# TPV 把函数名自动 bitcast 成函数指针(如 CreateThread 传函数)。
# 回调签名约定: def cb(w: bytes, data: bytes): ...(w=控件指针,data=用户数据)
# Notes:
# * This package only provides extern FFI (the fltk._cfl* modules) + a few Pythonic wrapper classes;
# all FLTK objects are opaque pointers; methods map to cfltk's Fl_<Class>_<action>.
# * The full C API is directly available via `from fltk._cfl import *` etc.
# * Callback support: a Viper module-level function can be passed to FLTK directly as an Fl_Callback--
# TPV auto-bitcasts the function name into a function pointer (e.g. CreateThread passes a function).
# Callback signature convention: def cb(w: bytes, data: bytes): ... (w=widget pointer, data=user data)
# def on_click(w: bytes, data: bytes):
# Fl_Widget_set_label(w, "Clicked!")
# btn.set_callback(on_click, 0)
# Fl.add_timeout(1.0, on_tick, 0)
# * 事件驱动也可用 Fl.check()/Fl.event*() 轮询。
# * Event-driven code may also poll with Fl.check()/Fl.event*().
# ============================================================
import t, c
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl.vp — cfl.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl.vp — extern FFI declarations for cfl.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_run() -> int
extern def Fl_check() -> int
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_box.vp — cfl_box.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_box.vp — extern FFI declarations for cfl_box.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Box (widget_declare) -----
extern def Fl_Box_new(x: int, y: int, width: int, height: int, title: str) -> Fl_BoxPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_browser.vp — cfl_browser.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_browser.vp — extern FFI declarations for cfl_browser.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Browser (widget_declare) -----
extern def Fl_Browser_new(x: int, y: int, width: int, height: int, title: str) -> Fl_BrowserPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_button.vp — cfl_button.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_button.vp — extern FFI declarations for cfl_button.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Button (widget_declare) -----
extern def Fl_Button_new(x: int, y: int, width: int, height: int, title: str) -> Fl_ButtonPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_dialog.vp — cfl_dialog.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_dialog.vp — extern FFI declarations for cfl_dialog.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_Native_File_Chooser_new(a0: int) -> Fl_Native_File_ChooserPtr
extern def Fl_Native_File_Chooser_delete(self: Fl_Native_File_ChooserPtr)
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_draw.vp — cfl_draw.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_draw.vp — extern FFI declarations for cfl_draw.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_set_color_int(c: uint32)
extern def Fl_set_color_rgb(r: uint8, g: uint8, b: uint8)
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_group.vp — cfl_group.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_group.vp — extern FFI declarations for cfl_group.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Group (widget_declare) -----
extern def Fl_Group_new(x: int, y: int, width: int, height: int, title: str) -> Fl_GroupPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_image.vp — cfl_image.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_image.vp — extern FFI declarations for cfl_image.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Image (image_declare) -----
extern def Fl_Image_draw(self: Fl_ImagePtr, X: int, Y: int, W: int, H: int)
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_input.vp — cfl_input.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_input.vp — extern FFI declarations for cfl_input.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Input (widget_declare) -----
extern def Fl_Input_new(x: int, y: int, width: int, height: int, title: str) -> Fl_InputPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_lock.vp — cfl_lock.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_lock.vp — extern FFI declarations for cfl_lock.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_mutex_lock()
extern def Fl_mutex_unlock()
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_menu.vp — cfl_menu.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_menu.vp — extern FFI declarations for cfl_menu.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Menu_Bar (widget_declare) -----
extern def Fl_Menu_Bar_new(x: int, y: int, width: int, height: int, title: str) -> Fl_Menu_BarPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_misc.vp — cfl_misc.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_misc.vp — extern FFI declarations for cfl_misc.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Spinner (widget_declare) -----
extern def Fl_Spinner_new(x: int, y: int, width: int, height: int, title: str) -> Fl_SpinnerPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_prefs.vp — cfl_prefs.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_prefs.vp — extern FFI declarations for cfl_prefs.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_Preferences_set_file_access(flags: uint32)
extern def Fl_Preferences_file_access() -> uint32
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_printer.vp — cfl_printer.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_printer.vp — extern FFI declarations for cfl_printer.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_Printer_new() -> Fl_PrinterPtr
extern def Fl_Printer_delete(self: Fl_PrinterPtr)
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_surface.vp — cfl_surface.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_surface.vp — extern FFI declarations for cfl_surface.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_Surface_Device_set_current(self: Fl_Surface_DevicePtr)
extern def Fl_Surface_Device_is_current(self: Fl_Surface_DevicePtr) -> int
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_table.vp — cfl_table.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_table.vp — extern FFI declarations for cfl_table.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Table (widget_declare) -----
extern def Fl_Table_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TablePtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_term.vp — cfl_term.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_term.vp — extern FFI declarations for cfl_term.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Terminal (widget_declare) -----
extern def Fl_Terminal_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TerminalPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_text.vp — cfl_text.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_text.vp — extern FFI declarations for cfl_text.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Text_Display (widget_declare) -----
extern def Fl_Text_Display_new(x: int, y: int, width: int, height: int, title: str) -> Fl_Text_DisplayPtr
+4 -4
View File
@@ -1,9 +1,9 @@
# ============================================================
# fltk/_cfl_theme.vp — Fl_Theme(C++ 主题库)→ Viper extern FFI
# fltk/_cfl_theme.vp — Fl_Theme (C++ theme library) -> Viper extern FFI
#
# 对应 Fl_Theme/fltk_theme.cpp 导出的 C 函数(手工维护,gen_ffi 不覆盖)。
# 链接需 libfltk_theme.a(见 project.vpj linker.static_libs)+
# libcfltk2.a + libfltk_images.a(Fl_SVG_Image)。
# Corresponds to the C functions exported by Fl_Theme/fltk_theme.cpp (hand-maintained, not overwritten by gen_ffi).
# Linking requires libfltk_theme.a (see project.vpj linker.static_libs) +
# libcfltk2.a + libfltk_images.a (Fl_SVG_Image).
# ============================================================
import t, c
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_tree.vp — cfl_tree.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_tree.vp — extern FFI declarations for cfl_tree.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Tree (widget_declare) -----
extern def Fl_Tree_new(x: int, y: int, width: int, height: int, title: str) -> Fl_TreePtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_utils.vp — cfl_utils.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_utils.vp — extern FFI declarations for cfl_utils.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
extern def Fl_filename_expand(out: str, len_: int, in_: str) -> int
extern def Fl_type_name(w: bytes) -> str
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_valuator.vp — cfl_valuator.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_valuator.vp — extern FFI declarations for cfl_valuator.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Slider (widget_declare) -----
extern def Fl_Slider_new(x: int, y: int, width: int, height: int, title: str) -> Fl_SliderPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_widget.vp — cfl_widget.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_widget.vp — extern FFI declarations for cfl_widget.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Widget (widget_declare) -----
extern def Fl_Widget_new(x: int, y: int, width: int, height: int, title: str) -> Fl_WidgetPtr
+4 -4
View File
@@ -2,12 +2,12 @@ import t, c
from fltk._types import *
# ============================================================
# fltk/_cfl_window.vp — cfl_window.h 的 extern FFI 声明(对接 cfltk C API)
# fltk/_cfl_window.vp — extern FFI declarations for cfl_window.h (cfltk C API)
# ============================================================
# 所有函数直接对应 cfltk 生成的 C 符号(extern "C"),链接目标: cfltk2。
# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;
# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。
# Every function maps directly to a cfltk-generated C symbol (extern "C"); link target: cfltk2.
# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;
# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).
# ----- Fl_Window (widget_declare) -----
extern def Fl_Window_new(x: int, y: int, width: int, height: int, title: str) -> Fl_WindowPtr
+1 -1
View File
@@ -1,7 +1,7 @@
import t, c
# ============================================================
# fltk/_enums.vp — cfltk 枚举常量(t.CInt)
# fltk/_enums.vp — cfltk enum constants (t.CInt)
# ============================================================
Fl_BrowserType_Normal: int = 0
+3 -3
View File
@@ -1,11 +1,11 @@
import t, c
# ============================================================
# fltk/_types.vp — FLTK 不透明对象指针 typedef(对接 cfltk C API)
# fltk/_types.vp — opaque FLTK object pointer typedefs (cfltk C API)
# ============================================================
# 所有 FLTK C++ 对象在 FFI 中一律按不透明指针处理,生命周期由
# cfltk/fltk 库管理(new/delete 见各 *_delete / *_free)。
# Every FLTK C++ object is handled as an opaque pointer in FFI; its lifetime is owned
# by the cfltk/fltk library (see each *_delete / *_free).
Fl_AdjusterPtr: t.CTypedef = t.CPtr
Fl_Anim_GIF_ImagePtr: t.CTypedef = t.CPtr
+57 -57
View File
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
"""
fltk/gen_ffi.py — cfltk (FLTK C API) → Viper extern FFI 生成器
fltk/gen_ffi.py - cfltk (FLTK C API) -> Viper extern FFI generator
从 cfltk/include/cfltk2/*.h 读取全部 C 声明:
* 解析所有 `#define XXX_DECLARE(param) ...` 宏定义(WIDGET/GROUP/WINDOW/
BUTTON/INPUT/BROWSER/IMAGE/TABLE/DISPLAY/VALUATOR/MENU),
* 按每个头文件里的 `XXX_DECLARE(Class)` 调用展开,
* 连同头文件中的独立函数原型一起转成 extern def。
Reads all C declarations from cfltk/include/cfltk2/*.h:
* Parse all `#define XXX_DECLARE(param) ...` macro definitions (WIDGET/GROUP/WINDOW/
BUTTON/INPUT/BROWSER/IMAGE/TABLE/DISPLAY/VALUATOR/MENU),
* Expand each `XXX_DECLARE(Class)` call in every header,
* Convert them, along with the standalone function prototypes in the headers, into extern def.
生成 includes/fltk 下的 .vp FFI 文件:
_types.vp 不透明指针 typedef(每个 Fl_* 类一个 Fl_*Ptr)
_enums.vp cfl_enums.h 的全部枚举常量
_cfl.vp 全局 Fl 函数(cfl.h)
_cfl_<header>.vp 各头文件对应 extern def
Generates the .vp FFI files under includes/fltk:
_types.vp opaque pointer typedefs (one Fl_*Ptr per Fl_* class)
_enums.vp all enum constants of cfl_enums.h
_cfl.vp global Fl functions (cfl.h)
_cfl_<header>.vp extern def per header file
用法: python includes/fltk/gen_ffi.py
Usage: python includes/fltk/gen_ffi.py
"""
import os
import re
@@ -23,15 +23,15 @@ HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "cfltk", "include", "cfltk2")
# ============================================================
# C → Viper 类型映射
# C -> Viper type mapping
# ============================================================
# 函数指针类型(Viper 无法表达,一律映射为不透明 bytes)
# Function pointer types (not expressible in Viper; always mapped to opaque bytes)
CALLBACK_TYPES = {
"Fl_Callback", "custom_handler_callback", "custom_draw_callback",
"Fl_Awake_Handler", "Fl_Text_Modify_Cb", "Fl_Text_Predelete_Cb",
}
# Viper/Python 关键字不能作参数名(C 里 `int Fl_compose(int *del)` 之类)
# Viper/Python keywords cannot be used as parameter names (e.g. C `int Fl_compose(int *del)`)
_KEYWORDS = {
"def", "class", "from", "import", "global", "nonlocal", "pass", "lambda",
"del", "in", "is", "and", "or", "not", "for", "while", "if", "elif",
@@ -40,8 +40,8 @@ _KEYWORDS = {
"int", "str", "float", "bool", "len",
}
# C 类型关键字:作无名参数末尾单词时属于类型的一部分而非参数名
# (`unsigned int` → 末尾 int 不是参数名;`Fl_color_average(unsigned int, ...)`)。
# C type keywords: when the last word of an unnamed parameter, they belong to the type, not the name
# (`unsigned int` -> trailing int is not the name; `Fl_color_average(unsigned int, ...)`).
_C_TYPE_WORDS = {
"int", "char", "short", "long", "unsigned", "signed", "float", "double",
"void", "size_t", "uintptr_t",
@@ -49,21 +49,21 @@ _C_TYPE_WORDS = {
def map_type(t: str) -> str | None:
"""返回 Viper 注解类型;void 返回 None。"""
"""Return the Viper annotation type; returns None for void."""
t = t.strip()
t = re.sub(r"\bconst\b", "", t).strip()
t = re.sub(r"\bstruct\b", "", t).strip()
t = re.sub(r"\s+", " ", t)
if t in CALLBACK_TYPES:
return "bytes"
if "(" in t: # 函数指针(未命名的 typedef 形态)
if "(" in t: # function pointer (unnamed typedef form)
return "bytes"
if t.endswith("[]"):
t = t[:-2].strip() + "*"
star_cnt = t.count("*")
if star_cnt:
base = t.replace("*", "").strip()
if star_cnt >= 2: # 指针的指针(int** 等)一律按不透明指针
if star_cnt >= 2: # pointer-to-pointer (int** etc.) always treated as opaque pointer
return "bytes"
if base == "char":
return "str"
@@ -100,11 +100,11 @@ def map_type(t: str) -> str | None:
return "t.CSizeT"
if t.startswith("Fl_") or t.startswith("custom_"):
return t + "Ptr"
return "bytes" # 未知类型一律按不透明指针处理
return "bytes" # unknown types always treated as opaque pointer
def split_args(args: str) -> list[tuple[str, str]]:
"""拆分 C 参数串 → [(类型, 参数名)],函数指针/无名参数自动补名。"""
"""Split a C argument string -> [(type, name)]; function pointers / unnamed args get an auto name."""
if not args.strip():
return []
parts: list[str] = []
@@ -125,19 +125,19 @@ def split_args(args: str) -> list[tuple[str, str]]:
out: list[tuple[str, str]] = []
for p in parts:
p = p.strip()
if not p or p == "void": # C 的 (void) 表示无参数
if not p or p == "void": # C's (void) means no parameters
continue
if "(" in p and (")(" in p or "*)" in p or re.search(r"\(\s*\*", p)):
out.append((p, "cb%d" % len(out)))
continue
# C 参数形如 `Fl_Tree *self` / `const char *title`:'*' 紧贴参数名,
# 末尾单词即为参数名,其余部分为类型
# A C parameter looks like `Fl_Tree *self` / `const char *title`: the '*' sticks to the name,
# so the last word is the name and the rest is the type
m = re.search(r"(\w+)\s*$", p)
if m:
typ = p[:m.start()].strip()
nm = m.group(1)
# 纯类型(无名)参数:无类型部分(`int`)或末尾单词本身是 C 类型
# 关键字(`unsigned int` → 末尾 int 是类型的一部分,非参数名)。
# Pure type (unnamed) parameter: no type part (`int`) or the last word is itself a C type
# keyword (`unsigned int` -> trailing int is part of the type, not the name).
if not typ or nm in _C_TYPE_WORDS:
typ, nm = p, "a%d" % len(out)
if nm in _KEYWORDS:
@@ -152,19 +152,19 @@ def emit_extern(name: str, params: list[tuple[str, str]], ret_t: str | None,
indent: str = "") -> str:
ps = ", ".join("%s: %s" % (pn, map_type(pt)) for pt, pn in params)
rt = map_type(ret_t) if ret_t else None
if rt is None: # void / 无返回
if rt is None: # void / no return
return "%sextern def %s(%s)" % (indent, name, ps)
return "%sextern def %s(%s) -> %s" % (indent, name, ps, rt)
# ============================================================
# 头文件解析
# Header parsing
# ============================================================
MACRO_CALL_RE = re.compile(r"(?m)^\s*(\w+_DECLARE)\((\w+)\)\s*$")
def strip_macro_blocks(text: str) -> str:
"""删除 #define 宏定义块(含全部反斜杠续行),逐行处理兼容 CRLF。"""
"""Remove #define macro definition blocks (including all backslash continuations); line-by-line to tolerate CRLF."""
out: list[str] = []
skip = False
for line in text.split("\n"):
@@ -183,19 +183,19 @@ def strip_macro_blocks(text: str) -> str:
def strip_preprocessor(text: str) -> str:
text = strip_macro_blocks(text)
text = re.sub(r"typedef[^;]*;", "", text, flags=re.S) # typedef
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) # 注释
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S) # comments
return text
def extract_protos(text: str) -> list[tuple[str, str, str]]:
"""提取独立函数原型 → [(返回类型, 函数名, 参数串)]。
"""Extract standalone function prototypes -> [(return type, function name, argument string)].
注意 C 的 `类型 *名字` 中 '*' 紧贴函数名、与类型之间才有空格,
因此不能用空白分隔的 `类型 名字(` 拆分;改为取第一个 '(' 之前末尾的单词为
函数名,其余为返回类型。
Note that in C `type *name` the '*' sticks to the function name and the space is before the type,
so it cannot be split by the whitespace-separated `type name(` form; instead take the last word
before the first '(' as the function name and the rest as the return type.
"""
text = strip_preprocessor(text)
text = re.sub(r"(?m)^\s*#.*$", "", text) # 其余预处理行
text = re.sub(r"(?m)^\s*#.*$", "", text) # other preprocessor lines
text = re.sub(MACRO_CALL_RE, "", text)
protos: list[tuple[str, str, str]] = []
buf: list[str] = []
@@ -206,7 +206,7 @@ def extract_protos(text: str) -> list[tuple[str, str, str]]:
elif ch == ")":
depth -= 1
if depth == 0 and ch in "{}":
buf = [] # extern "C" { / 块花括号不进入原型缓冲
buf = [] # extern "C" { / block braces do not enter the prototype buffer
continue
if ch == ";" and depth == 0:
stmt = "".join(buf).strip()
@@ -227,11 +227,11 @@ def extract_protos(text: str) -> list[tuple[str, str, str]]:
def extract_macro_defs(text: str) -> dict:
"""解析 `#define XXX_DECLARE(param)` 宏体 → 方法模板。
"""Parse `#define XXX_DECLARE(param)` macro bodies -> method templates.
返回 {宏名: (param, [(方法后缀, 参数串, 返回类型, 'SELF'|'PARAMS'), ...])}
'SELF' 表示首参为该类自身指针(展开时替换为 self: <类>Ptr),
'PARAMS' 表示保留参数原样(new/from_dyn_ptr/super_draw 等)。
Returns {macro name: (param, [(method suffix, argument string, return type, 'SELF'|'PARAMS'), ...])}
'SELF' means the first argument is the class's own pointer (replaced with self: <class>Ptr on expansion),
'PARAMS' means the arguments are kept as-is (new/from_dyn_ptr/super_draw etc.).
"""
out: dict = {}
lines = text.split("\n")
@@ -252,7 +252,7 @@ def extract_macro_defs(text: str) -> dict:
body.append(line)
break
methods: list = []
body_src = "\n".join(body).replace("##", "") # widget##_x → widget_x
body_src = "\n".join(body).replace("##", "") # widget##_x -> widget_x
for ret, fname, args in extract_protos(body_src):
fn = fname.replace("##", "")
suffix = fn[len(param):].lstrip("_")
@@ -268,7 +268,7 @@ def extract_macro_defs(text: str) -> dict:
def extract_typedefs(text: str) -> list[str]:
"""提取顶层 typedef 名称(先剥离 #define 宏体,避免误扫 widget 等宏形参)。"""
"""Extract top-level typedef names (strip #define macro bodies first to avoid scanning macro params like widget)."""
names = []
text = strip_macro_blocks(text)
for m in re.finditer(r"typedef\s+(?:struct\s+)?[\w\s\*\(\)]*?(\w+)\s*;", text):
@@ -309,7 +309,7 @@ def extract_enums(text: str) -> list[tuple[str, int]]:
# ============================================================
# 生成
# Generation
# ============================================================
CLASS_PTRS: set[str] = set()
@@ -320,7 +320,7 @@ def gen_class_methods(cls: str, param: str, methods) -> list[str]:
fname = "%s_%s" % (cls, suffix)
ret_t = re.sub(r"\b%s\b" % re.escape(param), cls, ret)
if kind == "SELF":
rest = split_args(args)[1:] # 丢弃宏里的 self 参数
rest = split_args(args)[1:] # drop the self parameter inside the macro
ps = [(cls + " *", "self")] + rest
else:
ps = split_args(args)
@@ -339,15 +339,15 @@ def gen_header(path: str, out_name: str, defs: dict) -> str:
"from fltk._types import *",
"",
"# ============================================================",
"# fltk/%s — %s 的 extern FFI 声明(对接 cfltk C API)" % (out_name, base),
"# fltk/%s — extern FFI declarations for %s (cfltk C API)" % (out_name, base),
"# ============================================================",
"",
"# 所有函数直接对应 cfltk 生成的 C 符号(extern \"C\"),链接目标: cfltk2。",
"# 类型约定: Fl_* 对象均为不透明指针 (Fl_XxxPtr);const char* → str;",
"# 函数指针/void* → bytes(Viper 端传 0/NULL 或不使用)。",
"# Every function maps directly to a cfltk-generated C symbol (extern \"C\"); link target: cfltk2.",
"# Type conventions: Fl_* objects are opaque pointers (Fl_XxxPtr); const char* -> str;",
"# function pointers / void* -> bytes (pass 0/NULL or leave unused on the Viper side).",
"",
]
# 按头文件中宏调用的顺序展开
# expand in the order of macro calls in the header
for mm in MACRO_CALL_RE.finditer(text):
kind, cls = mm.group(1), mm.group(2)
if kind in defs:
@@ -355,7 +355,7 @@ def gen_header(path: str, out_name: str, defs: dict) -> str:
lines.append("# ----- %s (%s) -----" % (cls, kind.lower()))
lines.extend(gen_class_methods(cls, param, methods))
lines.append("")
# 独立原型
# standalone prototypes
protos = extract_protos(text)
for ret, name, args in protos:
lines.append(emit_extern(name, split_args(args), ret))
@@ -366,7 +366,7 @@ def main() -> None:
os.makedirs(HERE, exist_ok=True)
headers = sorted(f for f in os.listdir(SRC) if f.endswith(".h"))
# 1. 收集全部宏定义 + 类指针 typedef
# 1. Collect all macro definitions + class pointer typedefs
defs: dict = {}
for h in headers:
with open(os.path.join(SRC, h), "r", encoding="utf-8",
@@ -395,11 +395,11 @@ def main() -> None:
"import t, c",
"",
"# ============================================================",
"# fltk/_types.vp — FLTK 不透明对象指针 typedef(对接 cfltk C API)",
"# fltk/_types.vp — opaque FLTK object pointer typedefs (cfltk C API)",
"# ============================================================",
"",
"# 所有 FLTK C++ 对象在 FFI 中一律按不透明指针处理,生命周期由",
"# cfltk/fltk 库管理(new/delete 见各 *_delete / *_free)。",
"# Every FLTK C++ object is handled as an opaque pointer in FFI; its lifetime is owned",
"# by the cfltk/fltk library (see each *_delete / *_free).",
"",
]
for p in sorted_ptrs:
@@ -416,7 +416,7 @@ def main() -> None:
"import t, c",
"",
"# ============================================================",
"# fltk/_enums.vp — cfltk 枚举常量(t.CInt)",
"# fltk/_enums.vp — cfltk enum constants (t.CInt)",
"# ============================================================",
"",
]
@@ -425,7 +425,7 @@ def main() -> None:
with open(os.path.join(HERE, "_enums.vp"), "w", encoding="utf-8") as f:
f.write("\n".join(enum_lines) + "\n")
# 4. 各头文件
# 4. Per header file
for h in headers:
if h in ("cfl_macros.h", "cfl_enums.h"):
continue
+7 -7
View File
@@ -1,18 +1,18 @@
# -*- coding: utf-8 -*-
# ============================================================
# gzip - 单文件 gzip 解码器(RFC 1952 + RFC 1951 deflate)
# gzip - single-file gzip decoder (RFC 1952 + RFC 1951 deflate)
#
# OOP 范式对齐 zipfile: 数据由 GzipFile 对象持有并负责释放;
# 内部位流/Huffman 助手为模块级私有函数。
# OOP paradigm aligned with zipfile: data is held and freed by the GzipFile object;
# internal bit-stream/Huffman helpers are module-level private functions.
#
# 用法:
# Usage:
# gz: rawptr[gzip.GzipFile] = gzip.GzipFile("train-images-idx3-ubyte.gz")
# if gz is None or gz.ok == 0: ...
# buf: bytes = gz.read_all() # 内部 malloc, read_all 返回即解压完成
# n: sizet = gz.dst_len # 原始长度(由对象持有, close 时释放)
# buf: bytes = gz.read_all() # internally malloc'd, decompression is complete when read_all returns
# n: sizet = gz.dst_len # original length (held by the object, freed on close)
# gz.close()
#
# 覆盖 stored/fixed/dynamic 三种 deflate 块; 不解 CRC。
# Covers the three deflate block types (stored/fixed/dynamic); does not decode CRC.
# ============================================================
import t, c
import stdlib
+1 -1
View File
@@ -5,7 +5,7 @@ import string
import viperio
# ============================================================
# JSON 值类型枚举
# JSON value type enum
# ============================================================
define JSON_NULL = 0
define JSON_BOOL = 1
+10 -10
View File
@@ -10,16 +10,16 @@ from . import (
# ============================================================
# 容量约定(cap 只由调用方显式传入,签名里没有默认值)
# write —— 三参入口,固定用 WRITE_CAP_DEFAULT(4096)作容量。
# write_cap —— 四参入口,容量由调用方显式传入。
# cap = 输出缓冲总字节数(含结尾 NUL)。所有写入先判容量:
# 写 n 字节要求 pos + n <= cap - 1(末位留给 write 收尾的 '\0')。
# 放不下时不写,并把 pos 置为"溢出标志" cap——此后任何写入都以
# `pos >= cap` 短路返回,最终 write 判 `end_pos >= cap` 返回 None。
# 即:内容超出容量时既不写穿缓冲,也不再继续写(绝不越界)。
# cap 的合法区间是 [WRITE_CAP_MIN, WRITE_CAP_MAX]:越界一律返回 None,
# 非法容量不进入 pool.alloc(不会按可疑值去申请内存)。
# Capacity contract (cap is passed explicitly only by the caller; there is no default in the signature)
# write -- three-arg entry point; always uses WRITE_CAP_DEFAULT (4096) as the capacity.
# write_cap -- four-arg entry point; the capacity is passed explicitly by the caller.
# cap = total bytes of the output buffer (including the trailing NUL). Every write first checks capacity:
# writing n bytes requires pos + n <= cap - 1 (the last byte is reserved for the '\0' that write appends).
# if it does not fit, nothing is written and pos is set to the "overflow marker" cap -- from then on any write returns early via
# the `pos >= cap` short-circuit, and the final write returns None when `end_pos >= cap`.
# That is: when the content exceeds the capacity it neither writes through the buffer nor keeps writing (never out of bounds).
# The legal range of cap is [WRITE_CAP_MIN, WRITE_CAP_MAX]: anything out of range always returns None,
# and an illegal capacity does not enter pool.alloc (no memory is requested using a suspicious value).
# ============================================================
define WRITE_CAP_MIN = 64# lower bound: shortest JSON doc (e.g. {"id":""}) incl NUL fits in 64 bytes
define WRITE_CAP_MAX = 67108864# upper bound: 64 MiB, not less than the largest output buffer a caller may request
+11 -11
View File
@@ -2017,19 +2017,19 @@ class MemBuddy(MemManager):
fb, self.free_count())
def dump_alive_orders(self):
"""OOM 归属画像:按 order 统计「记录在案且仍存活」的块,并给出整池账目。
"""OOM attribution profile: per order, counts blocks that are "recorded and still alive" and gives whole-pool accounting.
数据全部来自既有状态,无需 --detection:
- `chk_stack[0, chk_depth)` 是 alloc 时压栈的块地址;
- 块头为 `(order << 1) | 1` 表示存活,偶数表示已被单独 free(跳过,与
reset_to / check_alive_blocks 同判据)。
块大小 = MEMBUDDY_MIN_BLOCK << order,故这份分布能直接区分
“大量小块” 与 “少量大块”,是定位池被谁吃满的关键画像。
All data comes from existing state; --detection is not required:
- `chk_stack[0, chk_depth)` holds the block addresses pushed during alloc;
- a block header of `(order << 1) | 1` means alive, an even value means it was separately freed (skipped, using the
same criteria as reset_to / check_alive_blocks).
Block size = MEMBUDDY_MIN_BLOCK << order, so this distribution directly distinguishes
"many small blocks" from "a few large blocks", and is the key profile for locating who filled the pool.
整池账目(关键判据):池内每一字节必然属于「存活块」或「空闲块」二者之一,
故 `usable_size - Σ存活 - Σ空闲链表` 若显著不为 0,说明有块既不在空闲链表、
也没被任何记录项持有 —— 即空闲链表被写穿后**链被静默截断**(`_fl_pop` 允许
next==0 与池内任意对齐地址,截断不会报错),这些块从此再也分配不到:池漏满。
Whole-pool accounting (key criterion): every byte in the pool necessarily belongs to either a "live block" or a "free block",
so if `usable_size - sum(live) - sum(free lists)` is significantly non-zero, some blocks are neither in the free lists
nor held by any record entry -- i.e. after the free lists are written through, **the chain is silently truncated** (`_fl_pop` allows
next==0 and any in-pool aligned address, so truncation raises no error) and those blocks can never be allocated again: the pool leaks full.
"""
if self.chk_stack is None or self.usable is None:
return
+19 -19
View File
@@ -11,12 +11,12 @@ from .__values import Value, new_value, SSAValue
from .__function import Function, BasicBlock, block_append_text, block_set_terminated, block_get_terminated, function_move_block_to_end, _ll_name_needs_quote, block_insert_text_before_terminator
# ============================================================
# IRBuilder: 游标式 IR 指令发射器
# IRBuilder: cursor-style IR instruction emitter
#
# 跟踪当前 BasicBlock,将指令文本追加到块的指令链表。
# 维护递增的 SSA 名计数器(%0, %1, %2, ...)。
# Tracks the current BasicBlock and appends instruction text to the block's instruction list.
# Maintains an incrementing SSA name counter (%0, %1, %2, ...).
#
# 用法:
# Usage:
# builder = new_builder(pool, func)
# builder.PositionAtEnd(entry_block)
# a = builder.build_alloca(i32_ty)
@@ -25,24 +25,24 @@ from .__function import Function, BasicBlock, block_append_text, block_set_termi
# r = builder.build_load(i32_ty, a)
# builder.build_ret(r)
#
# 注意: 加 @t.NoVTable 以与硬编码 BUILDER_SIZE=40 保持一致
# (方法以实例方法实现,无虚表;文件末尾的同名顶层函数为一行委托)。
# Note: add @t.NoVTable to stay consistent with the hardcoded BUILDER_SIZE=40
# (methods are implemented as instance methods, with no vtable; the same-named top-level function at the end of the file is a one-line delegate).
# ============================================================
# 指令文本缓冲尺寸
# Instruction text buffer sizing
#
# MLIR 形态的类型文本会**内联具名结构体的完整字段体**
# (如 `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, …)>`),单个类型可达数百字节,
# 远长于 LLVM 形态的 `%"sha1.Cls"` 引用(十几字节)。故指令行缓冲必须按
# "一行最多内嵌几个类型文本"给足:_LINE_CAP 覆盖 5 个类型文本 + 固定部分。
# 渲染侧 TypePrint/MlirTypeText 已加溢出护栏:一旦截断即响亮报错,不再静默
# 产出畸形 MLIR(历史故障:alloca 行类型被 128 字节缓冲截断 → mlir-opt 报
# "unbalanced '(' character in pretty dialect name")。
# An MLIR-form type text **inlines the full field body of a named struct**
# (e.g. `!llvm.struct<"sha1.Cls", (i32, !llvm.ptr, ...)>`), so a single type can reach hundreds of bytes,
# far longer than the LLVM-form `%"sha1.Cls"` reference (a dozen-odd bytes). Therefore the instruction line buffer must be sized by
# "how many type texts can be embedded at most in one line": _LINE_CAP covers 5 type texts + the fixed part.
# The rendering side (TypePrint/MlirTypeText) already has an overflow guard: any truncation errors loudly instead of silently
# producing malformed MLIR (historical failure: the alloca line type was truncated by a 128-byte buffer -> mlir-opt reported
# "unbalanced '(' character in pretty dialect name").
# ============================================================
# 单个类型文本的容量(builders 内嵌类型经 TypePrint 渲染为 MLIR 形态)。
# 内联具名结构体在使用处展开**完整字段体**,类型文本可达数百字节(TransPyV
# 自身类名/嵌套更深)⇒ 512 偏小会触发 TypePrint 的"类型文本超出输出缓冲被
# 截断"响亮报错。提到 2048;注意 arg_cap 由本常量推导
# ((arg_count+1)*(_TY_TEXT_CAP+32)),过高会让多参调用单次分配逼近模块池单块上限。
# Capacity of a single type text (types embedded by the builders are rendered to MLIR form by TypePrint).
# An inlined named struct expands its **full field body** at the use site, so a type text can reach hundreds of bytes (TransPyV
# class names / nesting are deeper) => 512 is too small and would trigger TypePrint's "type text exceeds the output buffer and is
# truncated" loud error. Raised to 2048; note that arg_cap is derived from this constant
# ((arg_count+1)*(_TY_TEXT_CAP+32)), so a too-high value makes a single multi-arg call allocation approach the module pool's per-block limit.
_TY_TEXT_CAP: sizet = 2048
_LINE_CAP: sizet = 2560
+8 -8
View File
@@ -5,15 +5,15 @@ import string
import viperlib
# ============================================================
# mlirlite —— 与 llvmlite 逐名同形的 MLIR(llvm 方言) 文本发射库
# mlirlite -- an MLIR (llvm dialect) text emission library, isomorphic to llvmlite name for name
#
# 设计原则:
# * 内部模型(LLVMType/Value/Function/BasicBlock/IRBuilder/Module…)与
# includes/llvmlite **逐名逐签名同形**,因此 TPV 侧只需把
# `llvmlite.` 重命名为 `mlirlite.` 即可编译;
# * 差异只在"打印"阶段:TypePrint / ValuePrint / FunctionPrint /
# LLVMModulePrint 输出 MLIR 文本,而 builders 仍按原样生成中间指令文本;
# * 名字保留 LLVMxxx(同形优先)。语义差异见 README.md。
# Design principles:
# * The internal model (LLVMType/Value/Function/BasicBlock/IRBuilder/Module...) is
# **isomorphic to includes/llvmlite name for name and signature for signature**, so on the TPV side it suffices to rename
# `llvmlite.` to `mlirlite.` in order to compile;
# * The difference lies only in the "printing" stage: TypePrint / ValuePrint / FunctionPrint /
# LLVMModulePrint emit MLIR text, while the builders still generate the intermediate instruction text as before;
# * Names keep the LLVMxxx form (isomorphism first). See README.md for semantic differences.
# ============================================================
# Import submodules
+5 -5
View File
@@ -9,13 +9,13 @@ from .__types import LLVMType, TypePrint
# ============================================================
# Value: SSA 值表示
# Value: SSA value representation
#
# 表示一个 LLVM IR 值:SSA 临时值(%0/%result)或常量字面量(42/0)。
# 每个值有类型指针、名字、是否常量标志。
# 继承 GSListNode[Value] 获取强类型 Next: Value|CPtr(值池链表)
# Represents an LLVM IR value: an SSA temporary (%0/%result) or a constant literal (42/0).
# Each value has a type pointer, a name, and an is-constant flag.
# Inherits GSListNode[Value] to get a strongly-typed Next: Value|CPtr (the value pool list)
#
# 注意: 必须加 @t.NoVTable(理由见 __function.py 的 Line 注释)。
# Note: @t.NoVTable must be added (see the Line comment in __function.py for the reason).
# ============================================================
@t.NoVTable
class Value(GSListNode[Value]):
+9 -9
View File
@@ -9,17 +9,17 @@ from .__module import LLVMModule
# ============================================================
# SSA 验证器
# SSA verifier
#
# 与 llvmlite **逐名同形、语义相同**:验证的是**结构化内部模型 + 中间指令
# 文本**(builders 生成的 %N 定义/使用、块终止、phi 位置),
# 与 MLIR 渲染文本无关 —— MLIR 的合法性由 mlir-opt/mlir-translate 负责。
# Isomorphic to llvmlite **name for name and semantically identical**: it verifies the **structured internal model + intermediate instruction
# text** (the %N definitions/uses produced by the builders, block termination, phi placement),
# independent of the rendered MLIR text -- MLIR validity is the responsibility of mlir-opt/mlir-translate.
#
# 检查项:
# 1. 块终止: 每个基本块必须以终止指令结尾 (ret/br/switch/unreachable)
# 2. SSA 唯一性: 每个 %name 只能被定义一次
# 3. 未定义引用: 所有 %name 使用必须有对应定义
# 4. Phi 位置: phi 指令只能出现在块首(非 phi 指令之前)
# Checks:
# 1. Block termination: every basic block must end with a terminator (ret/br/switch/unreachable)
# 2. SSA uniqueness: every %name may be defined only once
# 3. Undefined references: every %name use must have a matching definition
# 4. Phi placement: a phi instruction may appear only at the head of a block (before any non-phi instruction)
# ============================================================
# Error codes
+11 -11
View File
@@ -1,19 +1,19 @@
# ============================================================
# openssl — Viper 的 OpenSSL 3.x FFI 绑定包
# openssl — Viper FFI binding package for OpenSSL 3.x
#
# 用户用法:
# Usage:
# import openssl
# v = openssl.OpenSSL_version(openssl.OpenSslVersion) # 版本文本
# v = openssl.OpenSSL_version(openssl.OpenSslVersion) # version text
# openssl.OPENSSL_init_ssl(0, None)
#
# 说明:
# * 全部为 extern FFI(extern def,直连 libcrypto/libssl 导出符号),
# 无 C++ 桥接层;对象均为不透明指针(openssl.types 里 *Ptr typedef)。
# * 头文件按 openssl 官方头分组:crypto/err/rand/sha/md5/hmac/objects/
# evp/bio/pem/x509/ssl。
# * 返回指针的函数返回 *Ptr,拿到对象后按需调用对应 *_free/SSL_free 等释放。
# * 密码回调类参数一律 bytes 占位,不使用时传 t.CPtr(0)/None。
# * 链接库:libcrypto + libssl(Windows 上 mingw 对应 -lcrypto -lssl)。
# Notes:
# * Everything is extern FFI (extern def, directly bound to libcrypto/libssl
# exported symbols), with no C++ bridge layer; objects are all opaque pointers (the *Ptr typedefs in openssl.types).
# * Headers are grouped by the official openssl headers: crypto/err/rand/sha/md5/hmac/objects/
# evp/bio/pem/x509/ssl.
# * Functions that return pointers return *Ptr; after acquiring an object, call the corresponding *_free/SSL_free etc. to release it as needed.
# * Password-callback parameters are always placeholder bytes; pass t.CPtr(0)/None when unused.
# * Linked libraries: libcrypto + libssl (on Windows with mingw, this means -lcrypto -lssl).
# ============================================================
import t
+3 -3
View File
@@ -2,10 +2,10 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/bio.vp — BIO(内存 / 文件 / Socket / Connect 抽象 IO)
# 对应头文件: bio.h
# openssl/bio.vp — BIO (memory / file / Socket / Connect abstract IO)
# Corresponding header: bio.h
#
# 含 BIO_ADDR/BIO_ADDRINFO:主机名解析(getaddrinfo 封装)+ TCP 连接/读写。
# Includes BIO_ADDR/BIO_ADDRINFO: hostname resolution (getaddrinfo wrapper) + TCP connect/read/write.
# ============================================================
# ---- BIO_METHOD accessors ----
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/crypto.vp — OpenSSL 全局初始化 / 版本 / 内存工具
# 对应头文件: crypto.h / opensslv.h
# openssl/crypto.vp — OpenSSL global initialization / version / memory utilities
# Corresponding headers: crypto.h / opensslv.h
# ============================================================
# ---- Version query types (type arg of OpenSSL_version(type)) ----
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/err.vp — OpenSSL 错误队列
# 对应头文件: err.h
# openssl/err.vp — OpenSSL error queue
# Corresponding header: err.h
# ============================================================
# unsigned long ERR_get_error(void) -- pop queue head and return error code
+4 -4
View File
@@ -2,11 +2,11 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/evp.vp — EVP 高层密码 API
# 对应头文件: evp.h
# openssl/evp.vp — EVP high-level crypto API
# Corresponding header: evp.h
#
# 包含:消息摘要(EVP_Digest*)、对称加解密(EVP_Cipher*/Encrypt*/Decrypt*)、
# Base64 编解码(EVP_EncodeBlock/DecodeBlock)、非对称(EVP_PKEY* 签名/验签/加解密)
# Includes: message digests (EVP_Digest*), symmetric encryption/decryption (EVP_Cipher*/Encrypt*/Decrypt*),
# Base64 encoding/decoding (EVP_EncodeBlock/DecodeBlock), asymmetric (EVP_PKEY* sign/verify/encrypt/decrypt)
# ============================================================
define EvpMaxMdSize = 64
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/hmac.vp — HMAC(一次性 + 上下文)
# 对应头文件: hmac.h
# openssl/hmac.vp — HMAC (one-shot + context)
# Corresponding header: hmac.h
# ============================================================
# ---- One-shot HMAC ----
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/md5.vp — MD5 一次性摘要(兼容用,非密码学安全)
# 对应头文件: md5.h
# openssl/md5.vp — MD5 one-shot digest (for compatibility, not cryptographically secure)
# Corresponding header: md5.h
# ============================================================
define Md5DigestLength = 16
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/objects.vp — ASN.1 对象(OID)与 NID
# 对应头文件: objects.h / obj_mac.h
# openssl/objects.vp — ASN.1 objects (OID) and NID
# Corresponding headers: objects.h / obj_mac.h
# ============================================================
# ---- Common NIDs (obj_mac.h constants) ----
+4 -4
View File
@@ -2,11 +2,11 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/pem.vp — PEM 编解码
# 对应头文件: pem.h
# openssl/pem.vp — PEM encoding/decoding
# Corresponding header: pem.h
#
# 全部走 BIO 输入(PEM_read_bio_*);证书/密钥的 x 出参可为 NULL
# (函数返回新对象)。回调 cb 用 bytes(Viper 传 t.CPtr(0))。
# Everything takes BIO input (PEM_read_bio_*); the x out-parameter for certificates/keys may be NULL
# (the function returns a new object). Callback cb uses bytes (Viper passes t.CPtr(0)).
# ============================================================
# ---- X509 certificate ----
+2 -2
View File
@@ -2,8 +2,8 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/rand.vp — 安全随机数
# 对应头文件: rand.h
# openssl/rand.vp — secure random numbers
# Corresponding header: rand.h
# ============================================================
# int RAND_bytes(unsigned char *buf, int num) -- cryptographically secure random
+4 -4
View File
@@ -2,11 +2,11 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/sha.vp — SHA-1 / SHA-2 一次性摘要
# 对应头文件: sha.h
# openssl/sha.vp — SHA-1 / SHA-2 one-shot digests
# Corresponding header: sha.h
#
# OpenSSL 3.x 推荐走 EVP(见 evp.vp: EVP_DigestInit_ex/Update/Final_ex),
# 这里保留传统一次性接口,方便快速算摘要。
# OpenSSL 3.x recommends using EVP (see evp.vp: EVP_DigestInit_ex/Update/Final_ex);
# the legacy one-shot interfaces are kept here for quick digest computation.
# ============================================================
define ShaDigestLength = 20
+6 -6
View File
@@ -2,17 +2,17 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/ssl.vp — SSL/TLS 连接层(握手 + SocketIO + 校验证书)
# 对应头文件: ssl.h
# openssl/ssl.vp — SSL/TLS connection layer (handshake + SocketIO + certificate verification)
# Corresponding header: ssl.h
#
# 典型客户端流程:
# Typical client flow:
# OPENSSL_init_ssl(...)
# ctx = SSL_CTX_new(TLS_client_method())
# SSL_CTX_set_verify(ctx, SslVerifyPeer, 0) # 校验对端
# SSL_CTX_set_verify(ctx, SslVerifyPeer, 0) # verify peer
# SSL_CTX_load_verify_locations(ctx, cafile, None)
# ssl = SSL_new(ctx)
# SSL_set_fd(ssl, fd) # 底层已连好的 TCP socket
# SSL_connect(ssl) # 握手
# SSL_set_fd(ssl, fd) # underlying TCP socket already connected
# SSL_connect(ssl) # handshake
# SSL_write/SSL_read
# ============================================================
+9 -9
View File
@@ -1,17 +1,17 @@
import t, c
# ============================================================
# openssl/types.vp — OpenSSL 3.x 不透明对象句柄 typedef
# openssl/types.vp — opaque object handle typedefs for OpenSSL 3.x
#
# 对接 libcrypto / libssl(OpenSSL 3.x 导出符号)。
# 对象一律按不透明指针 (void*) 处理,生命周期由 OpenSSL 库管理
# (X509_free/SSL_free/BIO_free/CRYPTO_free 等对应释放函数)。
# 链接目标:ViperOpenSSLProject 的 project.vpj 链接 openssl_358_C
# 自编译静态库(libcrypto.a / libssl.a)。
# Interfaces with libcrypto / libssl (OpenSSL 3.x exported symbols).
# Objects are always treated as opaque pointers (void*); their lifetime is
# managed by the OpenSSL library (corresponding X509_free/SSL_free/BIO_free/CRYPTO_free etc. release functions).
# Link target: project.vpj of ViperOpenSSLProject links openssl_358_C
# self-compiled static libraries (libcrypto.a / libssl.a).
#
# 注意:typedef 用 `t.CPtr` 表达(与 vqt6 一致),生成 i8*
# 指针参数,可被 ASan 插桩;纯 `t.CPtr` 会生成 opaque struct 参数,
# opt -passes=asan 会报 invalid type for function argument。
# Note: typedefs are expressed with `t.CPtr` (consistent with vqt6), which
# generates i8* pointer parameters that can be instrumented by ASan; a plain `t.CPtr` generates opaque struct parameters,
# and opt -passes=asan reports invalid type for function argument.
# ============================================================
# ---- Base contexts ----
+3 -3
View File
@@ -2,10 +2,10 @@ import t, c
from openssl.types import *
# ============================================================
# openssl/x509.vp — X509 证书解析 / 校验 + 必要 ASN.1 助手
# 对应头文件: x509.h / x509_vfy.h / asn1.h
# openssl/x509.vp — X509 certificate parsing / verification + required ASN.1 helpers
# Corresponding headers: x509.h / x509_vfy.h / asn1.h
#
# 证书网络解析场景:读取 subject/issuer/SAN/有效期/公钥、链式校验。
# Certificate network-parsing scenario: read subject/issuer/SAN/validity/public key, chain verification.
# ============================================================
define X509VOk = 0
+4 -4
View File
@@ -9,11 +9,11 @@ import zlib.pyzlib as pyzlib
# ============================================================
# requests/__init__.vp — HTTP(S) 客户端库包入口(Python requests 风格)
# requests/__init__.vp - HTTP(S) client library package entry point (Python requests style)
#
# 包结构:
# requests HTTP 客户端(纯 socket 实现,本文件)
# requests.https HTTPS 客户端(openssl SSL/TLS + 证书核验,见 https.vp)
# Package structure:
# requests HTTP client (pure socket implementation, this file)
# requests.https HTTPS client (openssl SSL/TLS + certificate verification, see https.vp)
#
# Usage:
# requests._mbuddy = my_mbuddy
+10 -10
View File
@@ -12,24 +12,24 @@ import w32.win32sync as wsync
# ============================================================
# requests/download.vp — 大文件切片下载(Range + 串行/多线程)
# requests/download.vp - large-file ranged download (Range + serial/multi-threaded)
#
# 用法:
# Usage:
# import requests.download as rd
# rc: INT = rd.download("https://host/big.bin", "big.bin", threads=4,
# chunk_size=262144, verify=True, ca_file="ca.crt")
# if rc != 0:
# print(rd.last_error())
#
# 策略:
# threads == 1 串行 Range 切片(顺序拉取并写入文件)
# threads > 1 多线程:文件按线程数均分为若干带(band),每线程一个
# 连接顺序下载自己带的切片,写入同一文件的对应偏移。
# Strategy:
# threads == 1 serial Range chunks (fetched and written to the file in order)
# threads > 1 multi-threaded: the file is split into bands by thread count, each thread uses one
# connection to download its band's chunks in order, writing at the corresponding offsets of the same file.
#
# 说明:
# - 通过 Content-Range 探测总大小与服务器 Range 支持;
# - http:// 走 requests(纯 socket),https:// 走 requests.https(openssl);
# - 每片请求不带 Accept-Encoding,响应为原始字节,直接落盘。
# Notes:
# - probes total size and server Range support via Content-Range;
# - http:// goes through requests (pure socket), https:// goes through requests.https (openssl);
# - each chunk request omits Accept-Encoding, the response is raw bytes and is written straight to disk.
# ============================================================
define DL_MAX_THREADS = 8
+8 -8
View File
@@ -11,9 +11,9 @@ import memhub
# ============================================================
# requests/https.vp — HTTPS 客户端(openssl SSL/TLS + 证书核验)
# requests/https.vp - HTTPS client (openssl SSL/TLS + certificate verification)
#
# 与 requests(HTTP)同名 API,但走 TLS 并默认校验对端证书:
# Same API names as requests (HTTP), but over TLS and by default it verifies the peer certificate:
# import requests.https as rhttps
# resp = rhttps.get("https://example.com/", verify=True)
# if resp is not None:
@@ -21,12 +21,12 @@ import memhub
# print(resp.text)
# requests.release(resp)
#
# 说明:
# - verify=True 时校验证书链 + 主机名(hostname 校验 SSL_set1_host),
# 提供 ca_file 指向 PEM CA bundle;不提供则走系统默认路径
# (Windows 上通常为空,真实站点会因"无法验证证书链"失败返回 None)。
# - 失败时返回 None,可用 last_error() 获取原因文本。
# - 握手成功后可用 last_tls_info() 查看 TLS 版本 / 加密套件 / 对端证书主题。
# Notes:
# - with verify=True it verifies the certificate chain + hostname (hostname check via SSL_set1_host),
# provide ca_file pointing to a PEM CA bundle; if not provided it uses the system default path
# (on Windows this is usually empty, so real sites fail with "unable to verify certificate chain" and return None).
# - on failure returns None; use last_error() to get the reason text.
# - after a successful handshake, use last_tls_info() to view the TLS version / cipher suite / peer certificate subject.
# ============================================================
define HTTPS_PORT = 443
+14 -14
View File
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
# ============================================================
# torch-lite: 仿 PyTorch 的极简张量/神经网络库(Viper)
# torch-lite: a minimal PyTorch-like tensor/neural-network library (Viper)
#
# 设计原则:
# * "足够用就行": 只覆盖 MNIST 两层 MLP 训练/推理所需的原语。
# * API 命名贴近 PyTorch: Tensor / linear_fwd(bias 融合) / relu /
# relu_backward / softmax_crossentropy(前向+梯度一体) / sgd_step。
# * 张量为 2D 稠密 double (rows x cols, 行主序), 权重形状同 PyTorch:
# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b。
# * 计算热点结合 vipersimd:
# - linear_fwd 内积按 k%4 用 simd_dot4d 累加(纯 AVX2 点积);
# - relu_fwd 用 simd_max4d(x, 0) 四路取大;
# - 向量乘标量用 simd_mul_scalar4d(归一化 /255 等)。
# backward / softmax / sgd 等按标量写(数据量小, 可读性优先)。
# * 内存: 所有 Tensor 及 data/grad 都从调用方传入的 memhub.MemBuddy
# 分配(与 includes 既有约定一致, pool.alloc 免手动 free)。
# Design principles:
# * "good enough": covers only the primitives needed to train/infer a two-layer MNIST MLP.
# * API names follow PyTorch closely: Tensor / linear_fwd(fused bias) / relu /
# relu_backward / softmax_crossentropy(forward+gradient combined) / sgd_step.
# * Tensors are 2D dense double (rows x cols, row-major), weight shapes match PyTorch:
# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b.
# * Compute hotspots use vipersimd:
# - linear_fwd dot product accumulates with simd_dot4d over k%4 (pure AVX2 dot product);
# - relu_fwd uses simd_max4d(x, 0) for 4-wide maximum;
# - vector-times-scalar uses simd_mul_scalar4d (normalization /255, etc.).
# backward / softmax / sgd, etc. are written scalar-wise (small data volume, readability first).
# * Memory: all Tensors and their data/grad are allocated from a caller-provided memhub.MemBuddy
# (consistent with the existing includes convention, pool.alloc avoids manual free).
# ============================================================
from stdint import *
import t, c
+352 -352
View File
@@ -10,420 +10,420 @@ import w32.win32process as wp
import w32.win32memory as wm
import w32.win32dbghelp as dbg
# 转储重入保护(见 DumpMainThread)**不使用 InterlockedCompareExchange**:该名字在 x64 上是
# winnt.h 的编译器内部函数(映射到 lock cmpxchg 指令),**不是可链接符号**,直接引用会在链接期
# 报 undefined symbol(实测:ld.lld: undefined symbol: InterlockedCompareExchange)。
# 本模块改用普通 32 位标志做「检查并占用」:并发回调最多导致一次重复转储(诊断工具可接受),
# 且不引入任何链接期依赖。
# Dump reentrancy protection (see DumpMainThread) does **NOT use InterlockedCompareExchange**: on x64 that
# name is a winnt.h compiler intrinsic (mapped to the lock cmpxchg instruction), **not a linkable symbol**, and
# referring to it directly fails at link time with an undefined symbol (observed: ld.lld: undefined symbol: InterlockedCompareExchange).
# This module instead uses a plain 32-bit flag for test-and-set: concurrent callbacks cause at most one duplicate
# dump (acceptable for a diagnostic facility) and introduce no link-time dependency.
# =============================================================================
# traceback — 运行时「按键取栈」模块(Ctrl+C / Ctrl+Break 时转储主线程调用栈)
# traceback -- runtime "press-key-to-capture-stack" module (dumps the main-thread call stack on Ctrl+C / Ctrl+Break)
#
# 解决的问题:编译器(或任何长任务)卡死时,用户按一个键就能拿到「现在卡在哪一行」
# 的完整调用栈——从 CRT 入口(mainCRTStartup / __tmainCRTStartup)到当前帧。
# Problem solved: when the compiler (or any long task) hangs, one keypress gives the user "where it is stuck right now"
# as a complete call stack -- from the CRT entry (mainCRTStartup / __tmainCRTStartup) down to the current frame.
#
# -----------------------------------------------------------------------------
# 一、触发语义(SetConsoleCtrlHandler 回调)
# 1. Trigger semantics (SetConsoleCtrlHandler callback)
# -----------------------------------------------------------------------------
# Ctrl+C (CTRL_C_EVENT) → 打印调用栈后**返回 FALSE**(交还系统默认处理 ⇒ 进程终止)
# Ctrl+Break (CTRL_BREAK_EVENT)→ 打印调用栈后**返回 TRUE**(已处理 ⇒ 进程继续运行)
# 其它控制事件(关窗/注销/关机,CTRL_CLOSE_EVENT 等)→ 一律返回 FALSE,不转储、不干预。
# **崩溃**(未处理异常,经 SetUnhandledExceptionFilter 捕获)→ 在**出错线程**上打印调用栈后
# 返回 EXCEPTION_CONTINUE_SEARCH(异常交还系统 ⇒ 退出码 / WER / ASan 报告行为原样保留),
# 见第十四节。
# 回调由系统在**新建的线程**上执行,不是主线程——所以「当前执行到哪」必须靠
# SuspendThread(主线程) + GetThreadContext 取得主线程寄存器,再回溯主线程的栈。
# 同一时刻**只允许一次转储**:连续按键会由系统在多个新线程上并发回调,而它们共用
# 同一个 LineBuf 与输出句柄 ⇒ 输出互相撕裂(实测出现过"降级:"行重复且交织)。
# 故 DumpMainThread 入口用内联汇编 `lock cmpxchg`(AtomicTryEnterDump)对 _DumpLock
# 做原子"检查并占用":抢占失败的调用直接返回(不挂起主线程、不输出任何内容)。
# 每次转储还会打印自增 Seq 与回调线程 id:若两条转储记录的 Seq 不同 ⇒ 顺序两次;
# 同一 Seq 的行却互相截断 ⇒ 说明仍存在共享缓冲的并发写(据此定位,不再靠猜)。
# Ctrl+C (CTRL_C_EVENT) -> prints the call stack, then **returns FALSE** (back to default handling => process terminates)
# Ctrl+Break (CTRL_BREAK_EVENT) -> prints the call stack, then **returns TRUE** (handled => process keeps running)
# Other control events (close/logoff/shutdown, CTRL_CLOSE_EVENT, etc.) -> always return FALSE, no dump, no intervention.
# **Crash** (unhandled exception, caught via SetUnhandledExceptionFilter) -> prints the call stack on the **faulting
# thread**, then returns EXCEPTION_CONTINUE_SEARCH (exception handed back to the system => exit code / WER / ASan report
# behavior is preserved); see section 14.
# The callback runs on a **newly created thread**, not the main thread -- so "where execution currently is" must come
# from SuspendThread(main thread) + GetThreadContext to obtain the main-thread registers, then walking its stack.
# **Only one dump at a time** is allowed: repeated keypresses make the system invoke the callback concurrently on
# several new threads, and they share the same LineBuf and output handle => output is torn apart (observed: duplicated,
# interleaved "degrade:" lines). So DumpMainThread's entry uses one inline-asm `lock cmpxchg` (AtomicTryEnterDump) to
# test-and-set _DumpLock atomically: a call that loses the race returns immediately (does not suspend the main
# thread, outputs nothing). Each dump also prints an auto-incrementing Seq and the callback thread id: two dump records
# with different Seqs => two sequential dumps; lines with the same Seq that truncate each other => concurrent writes to the shared buffer still exist (locate by this, not by guessing).
#
# -----------------------------------------------------------------------------
# 二、安装时机与总开关(本模块的开关落地点)
# 2. Install timing and master switch (where this module's switch lands)
# -----------------------------------------------------------------------------
# 约定:**旁文件存在** ⟺ 编译时用了 TPC 的 `--traceback`。
# Install() 先求 <当前 exe 完整路径> + ".stackmap";该文件不存在(CreateFileA 报
# ERROR_FILE_NOT_FOUND)则**立刻返回 0,零副作用**(不分配、不注册 handler);
# 存在才注册 SetConsoleCtrlHandler(并挂上未处理异常过滤器,见第十四节)。故 TPC 侧无需
# 生成任何其它开关代码,只需在启用该选项时让产物链接 dbghelp(见第五节)并调用一次
# Install()(见第六节)。
# 文件存在但其头部/记录区非法时:打印可定位的警告并把映射标记为不可用——调用栈仍
# 照常转储(只有地址),不会静默退出(诊断工具取不到信息时给降级信息,不掩盖错误)。
# Convention: **the sidecar file exists** <=> the build used TPC's `--traceback`.
# Install() first forms <current exe full path> + ".stackmap"; if that file is absent (CreateFileA reports
# ERROR_FILE_NOT_FOUND) it **returns 0 immediately with zero side effects** (no allocation, no handler registered);
# only when present does it register SetConsoleCtrlHandler (and attach the unhandled-exception filter, see
# section 14). So the TPC side needs no other switch code: it only has to make the artifact link dbghelp (see section 5)
# and call Install() once (see section 6) when the option is enabled.
# If the file exists but its header/record area is invalid: print a locatable warning and mark the mapping unusable -- the
# call stack is still dumped (addresses only); it never exits silently (a diagnostic gives degraded info instead of hiding errors).
#
# -----------------------------------------------------------------------------
# 三、旁文件(<exe>.stackmap)格式 —— 小端
# 3. Sidecar (<exe>.stackmap) format -- little-endian
# -----------------------------------------------------------------------------
# 头部 20 字节:magic="TPCS"(4B) + u32 version=1 + u32 entry_count + u32 str_count
# + u32 str_bytes
# 记录区 entry_count × 20B(**按 rva 升序**):u64 rva; u32 file_off; u32 line; u32 func_off
# 字符串池 str_bytes 字节:\0 结尾 UTF-8 串;file_off/func_off 是池内偏移
# (func_off = 0 表示无函数名)
# 运行时:GetModuleHandleA(NULL) 取映像基址,帧地址 - 基址 = rva,再二分查找记录区。
# 命中 → 帧行 " File \"<文件>\", line <行>, in <函数名>",其下再打一行 4 空格缩进的源码
# (运行期重开该文件读第 <行> 行;读不到就整行不打,见第十三节);
# 未命中 → " File \"<0x<地址> +0x<rva> no source line mapping>\", line 0, in <unknown>";
# 地址落在主模块之外(更低,典型为 DLL/系统帧)→ " File \"<0x<地址> external>\", line 0, in <unknown>"。
# 三种形态都把地址/RVA 保留在同一行(见第十三节)。
# 装载时做一次全表校验(rva 严格升序、字符串偏移落在池内),不合法即禁用映射——
# 二分查找依赖升序,破损的表会给出**错行号**,比"只打地址"更坏。
# 校验失败一律**逐项打印挂在哪一项**(含头部实读的 magic/version/count/str_bytes
# 与文件实际大小),失败原因不外泄为一句"旁文件非法"。
# ⚠ 读字节必须逐个 & 0xFF:Viper 的 Buf[i] 是**有符号** char,>= 0x80 的字节若直接
# 转 u32 会符号扩展(例:记录数 1000=0x3E8 的首字节 0xE8 → 0xFFFFFFE8),使头部的
# entry_count/str_bytes 变成天文数字、`20+count*20+str_bytes > 文件大小` 必然成立
# ⇒ 映射永远装载失败(这正是"源行映射=无"的真因,详见 ReadU32)。
# Header 20 bytes: magic="TPCS"(4B) + u32 version=1 + u32 entry_count + u32 str_count
# + u32 str_bytes
# Record area entry_count x 20B (**sorted by ascending rva**): u64 rva; u32 file_off; u32 line; u32 func_off
# String pool str_bytes bytes: NUL-terminated UTF-8 strings; file_off/func_off are pool offsets
# (func_off = 0 means no function name)
# Runtime: GetModuleHandleA(NULL) gets the image base, frame address - base = rva, then binary-search the record area.
# Hit -> frame line " File \"<file>\", line <line>, in <function>", followed by one more line of 4-space-indented source
# (at runtime the file is reopened and line <line> is read; if it cannot be read the whole line is omitted, see section 13);
# Miss -> " File \"<0x<address> +0x<rva> no source line mapping>\", line 0, in <unknown>";
# Address below the main module (typically a DLL/system frame) -> " File \"<0x<address> external>\", line 0, in <unknown>".
# All three forms keep the address/RVA on the same line (see section 13).
# Load time does one full-table validation (rva strictly ascending, string offsets inside the pool); if invalid the mapping is disabled --
# the binary search relies on ascending order, and a corrupt table yields **wrong line numbers**, worse than "addresses only".
# A validation failure always **prints which item it failed on** (including the actually-read header magic/version/count/str_bytes
# and the real file size); the reason is never collapsed into a single "sidecar invalid".
# NOTE: each byte read must be masked with 0xFF: Viper's Buf[i] is a **signed** char, so a byte >= 0x80 cast
# to u32 sign-extends (e.g. record count 1000=0x3E8, first byte 0xE8 -> 0xFFFFFFE8), making the header's
# entry_count/str_bytes astronomical and `20+count*20+str_bytes > file size` always true
# => the mapping can never load (this is the real cause of "source line mapping = none"; see ReadU32).
#
# -----------------------------------------------------------------------------
# 四、回溯两条路径与降级条件(**本轮已主副互换,见第十一节 B**)
# 4. The two walk paths and degrade conditions (**main/aux swapped this round, see section 11 B**)
# -----------------------------------------------------------------------------
# ⚠ 先读第十一节 B:本轮起**主路径 = RBP 链**(零 dbghelp 依赖),dbghelp 降为
# "可选加强"。下面这段 dbghelp 说明仍有效,只是它描述的是**加强路径**的历史细节。
# 加强路径(可选):dbghelp!StackWalk64(MachineType=IMAGE_FILE_MACHINE_AMD64)。
# NOTE: read section 11 B first: from this round **the main path = the RBP chain** (zero dbghelp dependency) and dbghelp is demoted
# to "optional augmentation". The dbghelp notes below still hold; they describe the historical detail of the **augmentation path**.
# Augmentation path (optional): dbghelp!StackWalk64 (MachineType=IMAGE_FILE_MACHINE_AMD64).
# SymSetOptions(SYMOPT_DEFERRED_LOADS|SYMOPT_UNDNAME|SYMOPT_NO_PROMPTS) +
# SymInitialize(GetCurrentProcess(), NULL, **TRUE**) —— TRUE 是硬要求:它让 dbghelp
# 枚举已加载模块填进自己的模块表;置 FALSE 时模块表为空,SymFunctionTableAccess64
# 取不到 RUNTIME_FUNCTION、SymGetModuleBase64 返回 0,StackWalk64 **首次调用即失败**
# (表现为"符号回溯=可用但 0 帧")。配合 DEFERRED_LOADS 不会真的去加载 PDB。
# 三个回调地址里 FunctionTableAccess/GetModuleBase 仍在 Install 时用 GetProcAddress
# 取(本仓既有的动态解析写法,先例 VET/App/lib/VGuard.vp:852-867):避免依赖
# 「跨模块导入的函数名作为函数指针实参」这一未经本仓验证的用法,取不到就传 NULL。
# ReadProcessMemory 回调改用**本模块内定义**的 ReadMemCb(同模块内"函数名作
# 函数指针实参"已由 CtrlHandler 先例验证):直接传 kernel32!ReadProcessMemory
# 本体时拿不到失败原因,而回调返回 0 会让 StackWalk64 立即终止(恰好是"0 帧")。
# 主路径(**RBP 链手工回溯**):无条件先走它。它拿不到更多帧(<=1)时**先走第二兜底
# = 栈扫描(ScanStack,不依赖帧指针)**;再之后才轮到 dbghelp 加强(编译期开关为 1、
# Install 时 SymInitialize/SymRefreshModuleList 成功、两个回调地址与帧缓冲齐备、且
# SymGetModuleBase64(主模块) 探针通过;**ASan 运行时存在只表示 DbgHelp 状态共享,不再
# 一票否决**);任一前提不满足都会打印明确原因(`[diag] symwalk=disabled reason=…`),
# **不静默 0 帧**。
# 本轮新增:每步无条件打 `[diag] rbp:` 原始读数 + 判定、以及 `walk: fallback=stack_scan`
# 扫描推断帧(`~` 前缀)——详见第十二节。
# 主路径实现:打印当前 RIP,然后沿主线程 RBP 链读 [rbp](调用者 rbp)与 [rbp+8]
# (返回地址)逐帧回溯;要求 rbp 8 字节对齐、严格向高地址增长(`[rbp] > rbp`)、
# 单步跨度 <= TRACEBACK_RBP_MAX_STEP、返回地址落在主模块基址区间内,否则立即停止
# (防野指针把回溯带飞);**读内存一律经 ReadProcessMemory,绝不直接解引用野 rbp**。
# RBP 链只覆盖保持帧指针的函数;帧指针一旦未保留([rbp+8] 不是返回地址)链即在第 1
# 步断——此时由 ScanStack 兜底,另叠一层 dbghelp 加强(可用时)。
# --traceback 时 TPC 给 llc 加 `-frame-pointer=all`(Phase2Translator.py:2187-2193);
# 该 flag 是否真正生效见第十二节 C 的调查结论与修法落点。
# 【取证:先取证,再决策】每次转储在 banner 之后打印一组前置诊断(**TRACEBACK_DIAG=1 时**)——
# Seq / 回调线程 id / 主线程 id / Dumping 进入前值 / MapOk / SymReady / ModInited /
# SymGetModuleBase64(主模块) 的**实际返回值** / 主模块基址 / 帧缓冲与两个回调地址
# 是否就位;进入 dbghelp 主路径后,**首帧**再打印 StackWalk64 的返回码、传入的
# machine/RIP/RSP/RBP、返回的 AddrPC,以及 SymFunctionTableAccess64(proc, AddrPC) 的
# 返回值。这些行**先于任何降级判断**产出,使"0 帧"的根因可直接判定(见第九节)。
# SymInitialize(GetCurrentProcess(), NULL, **TRUE**) -- TRUE is a hard requirement: it makes dbghelp
# enumerate loaded modules into its own module table; with FALSE the table is empty, SymFunctionTableAccess64
# cannot get a RUNTIME_FUNCTION, SymGetModuleBase64 returns 0, and StackWalk64 **fails on its very first call**
# (seen as "symbol walk = available but 0 frames"). Combined with DEFERRED_LOADS it does not actually load PDBs.
# Of the three callback addresses, FunctionTableAccess/GetModuleBase are still fetched in Install via GetProcAddress
# (this repo's existing dynamic-resolution style; precedent VET/App/lib/VGuard.vp:852-867): this avoids relying on
# "a cross-module imported function name as a function-pointer argument", unverified in this repo; pass NULL if unavailable.
# The ReadProcessMemory callback uses ReadMemCb **defined inside this module** (an in-module "function name as
# function-pointer argument" is already proven by the CtrlHandler precedent): passing kernel32!ReadProcessMemory
# itself gives no failure reason, and a 0 return makes StackWalk64 stop immediately (exactly the "0 frames").
# Main path (**manual RBP-chain walk**): always tried first. When it yields no more frames (<=1) it **first runs the second
# fallback = stack scan (ScanStack, no frame-pointer dependency)**; only after that comes the dbghelp augmentation (compile-time switch = 1,
# SymInitialize/SymRefreshModuleList succeeded at Install, both callback addresses and the frame buffer are ready, and
# the SymGetModuleBase64(main module) probe passes; **the presence of an ASan runtime only indicates DbgHelp state sharing, no longer
# a veto**); any unsatisfied precondition prints an explicit reason (`[diag] symwalk=disabled reason=...`),
# **never a silent 0 frames**.
# New this round: every step unconditionally prints `[diag] rbp:` raw readings + verdict, and `walk: fallback=stack_scan`
# plus scanned inferred frames (`~` prefix) -- see section 12.
# Main-path implementation: print the current RIP, then walk the main-thread RBP chain reading [rbp] (caller rbp) and [rbp+8]
# (return address) frame by frame; requires rbp to be 8-byte aligned, strictly increasing towards higher addresses (`[rbp] > rbp`),
# a single-step span <= TRACEBACK_RBP_MAX_STEP, and the return address within the main-module base range, else it stops at once
# (to keep a wild pointer from flying the walk off track); **all memory reads go through ReadProcessMemory, never a direct dereference of a wild rbp**.
# The RBP chain only covers functions that keep a frame pointer; once the frame pointer is not preserved ([rbp+8] is not the return address) the chain
# breaks at step 1 -- then ScanStack is the fallback, with a further dbghelp augmentation layer on top (when available).
# With --traceback, TPC adds `-frame-pointer=all` to llc (Phase2Translator.py:2187-2193);
# whether that flag actually takes effect is covered by the investigation result and fix location in section 12 C.
# [Evidence first: gather evidence, then decide] After the banner each dump prints a set of preflight diagnostics (**only when TRACEBACK_DIAG=1**) --
# Seq / callback thread id / main thread id / pre-entry Dumping value / MapOk / SymReady / ModInited /
# the **actual return value** of SymGetModuleBase64(main module) / main-module base / whether the frame buffer and the two callback addresses
# are in place; after entering the dbghelp main path the **first frame** additionally prints StackWalk64's return code, the
# machine/RIP/RSP/RBP passed in, the returned AddrPC, and the return value of SymFunctionTableAccess64(proc, AddrPC).
# These lines are produced **before any degrade decision**, so the root cause of "0 frames" can be judged directly (see section 9).
#
# -----------------------------------------------------------------------------
# 五、链接期依赖(TPC 侧必须知道)
# 5. Link-time dependencies (the TPC side must know this)
# -----------------------------------------------------------------------------
# dbghelp(-ldbghelp)+ kernel32(既有)。SymInitialize / StackWalk64 / SymSetOptions /
# SymGetModuleBase64 / SymFunctionTableAccess64 是**静态**引用(后两个用于首帧取证诊断),
# 产物必须能解析 dbghelp 的导入库,否则链接失败。若不便新增 -ldbghelp,
# 把 TRACEBACK_USE_DBGHELP 置 0:该常量比较会在编译期把唯一含 dbghelp 调用的分支
# 整体去掉(见 TRACEBACK_USE_DBGHELP 的注释),只保留 RBP 链回溯。
# dbghelp (-ldbghelp) + kernel32 (existing). SymInitialize / StackWalk64 / SymSetOptions /
# SymGetModuleBase64 / SymFunctionTableAccess64 are **static** references (the latter two are used for first-frame evidence),
# so the artifact must resolve dbghelp's import library or the link fails. If adding -ldbghelp is inconvenient,
# set TRACEBACK_USE_DBGHELP to 0: that constant comparison folds away at compile time the only branch containing
# dbghelp calls (see the TRACEBACK_USE_DBGHELP comment), leaving only the RBP-chain walk.
#
# -----------------------------------------------------------------------------
# 六、TPC 侧调用入口(符号与签名)
# 6. TPC-side call entry (symbol and signature)
# -----------------------------------------------------------------------------
# import traceback
# traceback.Install() # 返回 int:1=handler 已注册;0=未安装(无旁文件/失败)
# # 注册两个:SetConsoleCtrlHandler(Ctrl+C/Break)+
# # SetUnhandledExceptionFilter(崩溃),见第一节/第十四节
# 调用时机:进程启动期一次即可。两种等价做法——
# (a) 在入口 main 的第一条语句调用;
# (b) 注入一个 @llvm.global_ctors(.CRT$XCU 语义)构造器调用该符号(本仓先例:
# TransPyC/lib/core/LLVMCG/BaseGen.py:2061-2097 用 llvm.global_ctors 注册
# Ctrl+C 处理器)。构造器在 CRT 初始化期执行,能覆盖「main 之前就卡住」的场景。
# 本模块自身**没有**模块级构造器(状态对象由 Install 懒分配),故与 TPC 注入的
# 构造器不存在初始化顺序依赖,重复调用是幂等的。
# ⚠ 本模块把「主线程」定义为**调用 Install() 的那个线程**(Install 里 GetCurrentThreadId
# 记录其 id,回调时挂起/回溯的就是它)。故必须在真正的入口线程调用(main 或 CRT
# 构造器所在线程);若在启动期的工作线程里调用,转储的将是那个线程的栈。
# traceback.Install() # returns int: 1 = handler registered; 0 = not installed (no sidecar/failure)
# # registers two: SetConsoleCtrlHandler (Ctrl+C/Break) +
# # SetUnhandledExceptionFilter (crash), see section 1 / section 14
# Call timing: once during process startup. Two equivalent ways --
# (a) call it as the first statement of entry main;
# (b) inject an @llvm.global_ctors (.CRT$XCU semantics) constructor that calls this symbol (repo precedent:
# TransPyC/lib/core/LLVMCG/BaseGen.py:2061-2097 registers a Ctrl+C handler via llvm.global_ctors);
# the constructor runs during CRT initialization, covering the "stuck before main" case.
# This module itself has **no** module-level constructor (the state object is lazily allocated by Install), so it has
# no initialization-order dependency on TPC's injected constructor, and repeated calls are idempotent.
# NOTE: this module defines the "main thread" as **the thread that called Install()** (Install records its id
# via GetCurrentThreadId; that is the thread suspended/walked on callback). So it must be called on the real entry
# thread (main, or the thread running a CRT constructor); if called on a startup worker thread, the dump is that thread's stack.
#
# -----------------------------------------------------------------------------
# 七、为什么直接 WriteFile 而不是 printf
# 7. Why WriteFile directly instead of printf
# -----------------------------------------------------------------------------
# 转储发生在「主线程已被挂起」的时刻:若主线程恰好停在 CRT 的 stdout 锁内,回调线程
# 再调用 printf 会**永久等待同一把锁**,把"卡死"变成"按了键彻底不动"。
# 故输出走 kernel32!WriteFile(GetStdHandle(STD_ERROR_HANDLE))(无用户态 CRT 流锁),
# 格式化走 CRT snprintf(只写内存缓冲,不碰 FILE 锁)。stderr 的另一好处:stdout 常被
# 管道占用(如被父工具读取),stderr 一般直达终端。句柄与全部缓冲都在 Install 时备好,
# 回调线程**零分配**(MemBuddy 非线程安全,回调里绝不触碰池)。
# The dump happens at a moment when **the main thread is already suspended**: if the main thread happens to be stopped inside
# the CRT's stdout lock, the callback thread calling printf would **wait forever on the same lock**, turning "hang" into "key press and nothing moves".
# So output goes through kernel32!WriteFile(GetStdHandle(STD_ERROR_HANDLE)) (no user-mode CRT stream lock),
# and formatting goes through CRT snprintf (writes only to a memory buffer, never touching FILE locks). Another benefit of stderr: stdout is often
# taken by a pipe (e.g. read by a parent tool) while stderr usually goes straight to the terminal. Handles and all buffers are prepared at Install,
# and the callback thread does **zero allocation** (MemBuddy is not thread-safe; the callback never touches the pool).
#
# -----------------------------------------------------------------------------
# 七之二、转储**同时落文件** + 控制台纯 ASCII(本轮的两次修正)
# 7b. Dump **also written to a file** + pure-ASCII console (two fixes this round)
# -----------------------------------------------------------------------------
# (a) 落文件(解决"没有 Break 键"与"控制台代码页吞字"):Install 成功时以
# `<exe 完整路径> + ".stackdump.txt"` 打开一个**追加写**句柄(OPEN_ALWAYS +
# FILE_APPEND_DATA,不存在则创建),EmitLine 把**同一份内容**既写控制台又追加落盘。
# 文件是 UTF-8 字节原样,不受控制台代码页影响;每次转储在段前追加一小段分隔头
# (本地时间戳/pid/触发键/主线程 id/Seq,中文标签,仅文件)。文件写失败**不阻断**
# 控制台,且只提示一次(见 EmitToFile)。Install 成功时打印一行
# `[traceback] stack dump also written to: <路径>` 便于确认去向。
# (b) 控制台纯 ASCII:原先的运行时文本是中文(UTF-8 字节),而 cmd 的控制台输出代码页
# 默认为 936(GBK)——WriteFile 到控制台会按 936 解释 UTF-8 字节,一段多字节在多次
# WriteFile 之间被拆开/重组,表现为**整行消失**(最长的"首帧"行首当其冲)或相邻行错位、
# 看似"重复且残缺"(含 '�')。故**运行时输出到控制台的文本一律改英文/ASCII**
# (banner / [diag] / Trigger= / exe base= / walk: primary=rbp_chain / frames=N 等),
# 任何代码页下都可读、可 grep;` File "<文件>", line <行>, in <函数名>` 帧行保留源路径
# 原文(分隔符/新增说明文字一律 ASCII)。
# 中文仅保留在:本文件的注释/文档串、以及**只写文件**的分隔头。
# (a) File output (solves "no Break key" and "console code page eats characters"): on successful Install, open an
# **append** handle for `<exe full path> + ".stackdump.txt"` (OPEN_ALWAYS +
# FILE_APPEND_DATA, creating it if absent); EmitLine writes **the same content** both to the console and appending to disk.
# The file is raw UTF-8 bytes, unaffected by the console code page; each dump prepends a small separator header
# (local timestamp/pid/trigger key/main thread id/Seq, Chinese labels, file only). A file write failure **does not block**
# the console and is reported only once (see EmitToFile). On successful Install it prints a line
# `[traceback] stack dump also written to: <path>` to confirm the destination.
# (b) Pure-ASCII console: the runtime text was originally Chinese (UTF-8 bytes), while cmd's console output code page
# defaults to 936 (GBK) -- WriteFile to the console interprets UTF-8 bytes as 936, and a multi-byte sequence split/rejoined
# across several WriteFile calls appears as **whole lines disappearing** (the longest "first frame" line first) or adjacent lines shifting,
# looking "duplicated and mangled" (including the U+FFFD replacement character). So **all runtime text sent to the console is changed to English/ASCII**
# (banner / [diag] / Trigger= / exe base= / walk: primary=rbp_chain / frames=N, etc.),
# readable and greppable under any code page; the ` File "<file>", line <line>, in <function>` frame line keeps the source path
# text as-is (separators/added explanatory text are always ASCII).
# Chinese is kept only in: this file's comments/docstrings, and the **file-only** separator header.
#
# -----------------------------------------------------------------------------
# 八、依赖的既有声明(未改动)与本模块新增声明
# 8. Existing declarations relied on (unchanged) and declarations added by this module
# -----------------------------------------------------------------------------
# 既有(`from w32.win32base import *` 取裸名):GetModuleHandleA / GetProcAddress /
# Existing (`from w32.win32base import *` brings bare names): GetModuleHandleA / GetProcAddress /
# CloseHandle / GetLastError / INVALID_HANDLE_VALUE / ERROR_FILE_NOT_FOUND /
# LARGE_INTEGER / SYSTEMTIME / GetLocalTime / HANDLE / BOOL / ULONG / VOIDPTR;
# LARGE_INTEGER / SYSTEMTIME / GetLocalTime / HANDLE / BOOL / ULONG / VOIDPTR;
# wf.CreateFileA / wf.ReadFile / wf.WriteFile / wf.GetFileSizeEx / wf.GetStdHandle /
# wf.GENERIC_READ / wf.FILE_SHARE_READ / wf.FILE_SHARE_WRITE / wf.OPEN_EXISTING /
# wf.OPEN_ALWAYS / wf.FILE_ATTRIBUTE_NORMAL /
# wf.STD_ERROR_HANDLE / wf.STD_OUTPUT_HANDLE;
# wf.STD_ERROR_HANDLE / wf.STD_OUTPUT_HANDLE;
# wp.OpenThread / wp.SuspendThread / wp.ResumeThread / wp.GetCurrentThreadId /
# wp.GetCurrentProcess / wp.GetCurrentProcessId / wp.THREAD_SUSPEND_RESUME /
# wp.THREAD_GET_CONTEXT;
# string.memset / string.strcpy / stdlib.malloc / free / c.Addr。
# 本轮新增:wc.SetConsoleCtrlHandler / wc.CTRL_C_EVENT / wc.CTRL_BREAK_EVENT;
# wp.CONTEXT(x64 完整布局)/ wp.CONTEXT_FULL / wp.GetThreadContext;
# wm.ReadProcessMemory(由本模块 ReadMemCb 包装后作 StackWalk64 回调);
# w32/win32dbghelp.vp(STACKFRAME64/ADDRESS64/KDHELP64 + SymSetOptions/
# wp.THREAD_GET_CONTEXT;
# string.memset / string.strcpy / stdlib.malloc / free / c.Addr.
# New this round: wc.SetConsoleCtrlHandler / wc.CTRL_C_EVENT / wc.CTRL_BREAK_EVENT;
# wp.CONTEXT (full x64 layout) / wp.CONTEXT_FULL / wp.GetThreadContext;
# wm.ReadProcessMemory (wrapped by this module's ReadMemCb as the StackWalk64 callback);
# w32/win32dbghelp.vp (STACKFRAME64/ADDRESS64/KDHELP64 + SymSetOptions/
# SYMOPT_* + SymInitialize/SymFunctionTableAccess64/SymGetModuleBase64/
# StackWalk64);
# 转储文件:wf.WriteFile 追加写 <exe>.stackdump.txt + 自备常量
# TRACEBACK_FILE_APPEND_DATA(FILE_APPEND_DATA,wf 未定义);
# 转储重入保护用内联汇编的一条 `lock cmpxchg`(AtomicTryEnterDump)对模块级
# 32 位 _DumpLock 做原子"检查并占用"(不用 InterlockedCompareExchange:x64 上是
# 编译器内部函数、无导出符号、不可链接)。
# 本轮新增(真 Python traceback 输出):kernel32!GetEnvironmentVariableA 的**本模块自声明**
# (win32base 未声明,仓库仅 VET/App/lib/VDirs.vp:45 有;kernel32 本就链接,TPC/TPV 均
# 可解析),用于 TRACEBACK_DIAG 开关;其余仅用既有 API(见第十三节)。
# StackWalk64);
# Dump file: wf.WriteFile appends to <exe>.stackdump.txt + this module's own constant
# TRACEBACK_FILE_APPEND_DATA (FILE_APPEND_DATA, not defined by wf);
# Dump reentrancy protection uses one inline-asm `lock cmpxchg` (AtomicTryEnterDump) on the module-level
# 32-bit _DumpLock for atomic test-and-set (not InterlockedCompareExchange: on x64 it is
# a compiler intrinsic, has no exported symbol, and is not linkable).
# New this round (real Python traceback output): **this module's own declaration** of kernel32!GetEnvironmentVariableA
# (not declared by win32base; the repo declares it only in VET/App/lib/VDirs.vp:45; kernel32 is linked anyway, so both TPC and
# TPV can resolve it), used for the TRACEBACK_DIAG switch; everything else uses only existing APIs (see section 13).
#
# -----------------------------------------------------------------------------
# 九、"0 帧"的可执行结论(哪种情形 → 看哪个现场字段)
# 9. Actionable conclusions for "0 frames" (which case -> which live field to look at)
# -----------------------------------------------------------------------------
# ⚠ 本节所有取证行都是 `[diag]` 行,故**只在 TRACEBACK_DIAG=1 时产出**(见第十三节 C);
# 排查"0 帧"时请先用 `set TRACEBACK_DIAG=1` 启动目标进程。
# 首帧取证两行(" [diag] StackWalk64 first: ret=… addrpc=… fta=…" 与紧随其后的
# " [diag] StackWalk64 in: machine=… rip=… rsp=… rbp=…"——原为单条 143 字符长行,
# 拆成两条短行以消除窄终端折行造成的"显示残缺"假象,字段一个不减)
# 与前置诊断行("[diag] Seq=… … modinited=…" / "SymGetModuleBase64(main)=…")合起来直接给出结论:
# (1) modinited == 0 且 SymGetModuleBase64(main) == 0 ⇒ dbghelp 的模块表不含主模块。
# 根因是 SymInitialize 的 fInvadeProcess 参数(必须 TRUE)或调用时机(要在进程启动期、
# 模块枚举可用时调用,本模块放在 Install 里)。修法:确认 Install 里 SymInitialize 的
# 第三个实参为 1,且 Install 在入口线程、模块全部加载后调用;必要时改用
# SymRefreshModuleList 重新枚举。
# (2) 首帧 ret == 0,且紧邻有 "[diag] readmem failed addr=… size=… GetLastError=…"
# ⇒ 读栈内存失败。`ReadProcessMemory` 在回调里返回 0 会让 StackWalk64 立刻终止(=0 帧)。
# 修法:核对 ReadMemCb 的宽度口径(dbghelp 侧是 DWORD nSize + LPDWORD,kernel32 侧是
# SIZE_T,本模块已用本地 sizet 承接再写回 DWORD)与进程句柄/权限。
# (3) 首帧 ret != 0 但返回 addrpc == 0,或 fta(SymFunctionTableAccess64(addrpc))== 0
# ⇒ 上下文/模块基址口径问题(传给 StackWalk64 的 RIP/RSP/RBP 或模块基址不对)。
# 修法:核对 CONTEXT 的 Rip/Rsp/Rbp 取值与 GetModuleHandleA(NULL) 基址。
# 补充:首帧行里 `ret=not-called` 表示 SymInitialize/回调地址/帧缓冲前提不全、根本没调用
# StackWalk64(此时该行仍会产出,保证"转 RBP 链"之前永远有首帧证据)。
# 三者互斥且可判:只要把上面两组打印字段与判据对照,就能**当场**确定属于哪一类。
# NOTE: every evidence line in this section is a `[diag]` line, so it is **produced only when TRACEBACK_DIAG=1** (see section 13 C);
# when investigating "0 frames" first start the target process with `set TRACEBACK_DIAG=1`.
# The two first-frame evidence lines (" [diag] StackWalk64 first: ret=... addrpc=... fta=..." and the one immediately after,
# " [diag] StackWalk64 in: machine=... rip=... rsp=... rbp=..." -- originally one 143-char line,
# split into two short lines to remove the "mangled display" illusion caused by wrapping in a narrow terminal, with no field dropped)
# together with the preflight lines ("[diag] Seq=... ... modinited=..." / "SymGetModuleBase64(main)=...") directly give the conclusion:
# (1) modinited == 0 and SymGetModuleBase64(main) == 0 => dbghelp's module table does not contain the main module.
# Root cause is SymInitialize's fInvadeProcess argument (must be TRUE) or the call timing (it must be called during process
# startup while module enumeration is available; this module does it in Install). Fix: confirm Install's SymInitialize
# third argument is 1, and that Install is called on the entry thread after all modules are loaded; if needed use
# SymRefreshModuleList to re-enumerate.
# (2) first frame ret == 0 with an adjacent "[diag] readmem failed addr=... size=... GetLastError=..."
# => reading stack memory failed. A `ReadProcessMemory` returning 0 in the callback makes StackWalk64 stop at once (= 0 frames).
# Fix: check ReadMemCb's width convention (dbghelp side is DWORD nSize + LPDWORD, kernel32 side is
# SIZE_T; this module receives into a local sizet then writes back as DWORD) and the process handle/privileges.
# (3) first frame ret != 0 but returned addrpc == 0, or fta (SymFunctionTableAccess64(addrpc)) == 0
# => a context/module-base convention problem (the RIP/RSP/RBP passed to StackWalk64 or the module base is wrong).
# Fix: check CONTEXT's Rip/Rsp/Rbp values against the GetModuleHandleA(NULL) base.
# Note: `ret=not-called` in the first-frame line means the SymInitialize/callback-address/frame-buffer preconditions are incomplete and
# StackWalk64 was never called (the line is still produced then, guaranteeing first-frame evidence always exists before "switching to the RBP chain").
# The three cases are mutually exclusive and decidable: just compare the two printed field groups above against the criteria to determine **on the spot** which case it is.
#
# -----------------------------------------------------------------------------
# 十、本轮新增的两组定位打印(应答"walk 到底跑到没有"与"重复残缺真因")
# 10. Two groups of locating prints added this round (answering "did the walk run at all" and "the real cause of duplicate/mangled")
# -----------------------------------------------------------------------------
# A. walk 是否被调用、走了哪条出口(全部走 DumpMainThread / WalkViaDbgHelp 的既有输出通道;
# 这些行**只在 TRACEBACK_DIAG=1 时输出**):
# * DumpMainThread 在**调用前**打 " [diag] walk: enter use_dbghelp=… symready=…
# modinited=… frame_buf=… mapok=… asan=…",**调用后**打 " [diag] walk: leave
# frames=… mapok=…"。两行都在 ⇒ walk 一定进了 RBP 主路径(dbghelp 加强可选)。
# * 若 **连 walk: enter 都不出现**(且 TRACEBACK_DIAG=1)⇒ DumpMainThread 在本行之前就返回:
# 只可能是三处 fallback(OpenThread / SuspendThread / GetThreadContext 失败,各自都有
# "fallback: … cannot dump stack" 行)——据该行即可定位被跳过的分支。
# * WalkViaDbgHelp 的**每一条 return** 都先打 " [diag] walk: return reason=<…> frames=…",
# reason ∈ {null_state, disabled, null_frame_buf, null_context, stackwalk_first_failed,
# stackwalk_failed, addrpc_zero, frame_limit}(含义见函数内注释),故 walk 的走向
# 必可判定、不再靠猜。本轮起主路径是 RBP 链,DumpMainThread 另打
# `walk: primary=rbp_chain` / `walk: rbp_chain frames=…` / `walk: dbghelp enhancement engaged`;
# dbghelp 不可用时另打 `[diag] symwalk=disabled reason=…`(见第十一节 B)。
# B. "重复残缺"真因的判别凭据(唯一凭据 = 行号是否重复):
# * EmitLine 在 **TRACEBACK_DIAG=1** 时给**每一行**加全局递增前缀 " [dump#<k>] "
# (k 由 _DumpLineNo 每次 +1);关闭(默认)时**不加前缀**,使帧行保持纯 Python
# traceback 形态(见第十三节)。
# * 同一段文本出现多份且**序号重复**(同一 k 出现两次)⇒ 该文本被写了两次,即**并发写
# 同一 LineBuf**;此时才需要"输出互斥"。同一序号不可能出自单线程的一次 EmitLine。
# * 文本被**截断/换行错位**但**序号严格递增、无重复** ⇒ 内容只写了一次(乱不了号),
# 只可能是**终端长行折行/回显**层的显示效果;修法是缩短长行(本轮已把三条最长行拆短),
# **不动锁**。
# * 本轮补打:`[dump#skip]` 仅在使用输出互斥时才需要;本模块判定为"显示层",
# 故未加互斥、未打印该标记(判据与结论见交付说明)。
# A. Whether the walk was called and which exit it took (all via DumpMainThread / WalkViaDbgHelp's existing output channels;
# these lines are **output only when TRACEBACK_DIAG=1**):
# * DumpMainThread prints, **before the call**, " [diag] walk: enter use_dbghelp=... symready=...
# modinited=... frame_buf=... mapok=... asan=...", and, **after the call**, " [diag] walk: leave
# frames=... mapok=...". Both lines present => the walk did enter the RBP main path (dbghelp augmentation optional).
# * If **even walk: enter does not appear** (and TRACEBACK_DIAG=1) => DumpMainThread returned before this line:
# it can only be one of three fallbacks (OpenThread / SuspendThread / GetThreadContext failing, each with its own
# "fallback: ... cannot dump stack" line) -- that line locates the skipped branch.
# * **Every return** in WalkViaDbgHelp first prints " [diag] walk: return reason=<...> frames=...",
# with reason in {null_state, disabled, null_frame_buf, null_context, stackwalk_first_failed,
# stackwalk_failed, addrpc_zero, frame_limit} (meanings in the in-function comments), so the walk's path
# is always decidable, never guessed. Since this round the main path is the RBP chain; DumpMainThread additionally prints
# `walk: primary=rbp_chain` / `walk: rbp_chain frames=...` / `walk: dbghelp enhancement engaged`;
# when dbghelp is unavailable it additionally prints `[diag] symwalk=disabled reason=...` (see section 11 B).
# B. Evidence for the real cause of "duplicate/mangled" (the only evidence = whether line numbers repeat):
# * EmitLine adds a globally increasing prefix " [dump#<k>] " to **every line** when **TRACEBACK_DIAG=1**
# (k is incremented per call via _DumpLineNo); when off (default) it adds **no prefix**, keeping frame lines as plain Python
# traceback form (see section 13).
# * The same text appearing several times with **repeated numbers** (the same k twice) => that text was written twice, i.e. **concurrent writes to
# the same LineBuf**; only then is "output mutual exclusion" needed. The same number cannot come from a single-threaded EmitLine.
# * Text **truncated / line-wrapped out of place** but with **strictly increasing, non-repeating numbers** => the content was written once (numbers cannot be scrambled),
# so it can only be a **terminal long-line wrap/echo** display effect; the fix is to shorten long lines (this round split the three longest),
# **without touching the lock**.
# * Added this round: `[dump#skip]` is only needed when output mutual exclusion is used; this module judged it a "display-layer"
# issue, so no mutual exclusion was added and that marker is not printed (criteria and conclusion in the delivery notes).
#
# -----------------------------------------------------------------------------
# 十一、本轮两根因修复(崩溃 = 属性访问被降级;0 帧 = dbghelp 与环境互斥)
# 11. Two root-cause fixes this round (crash = attribute access silently demoted; 0 frames = dbghelp conflicting with the environment)
# -----------------------------------------------------------------------------
# A. **崩溃根因不是分配失败,而是 TPC 把 `_State.<字段>` 静默降级成常量 null。**
# 实测证据(构建产物 IR,模块前缀 = 本文件 sha1 前 16 位 9b4ee486e08837c5):
# * 源码 `string.memset(_State.FrameBuf, 0, …)` 被生成为
# `call i8* @memset(i8* null, i32 0, i64 248)` —— 参数就是字面 null;
# 同一函数里 `_State.LineBuf`(snprintf)、`_State.FuncTableAddr / ModuleBaseAddr`
# (StackWalk64 的两个回调槽)同样全被折成 null。
# * `Install` 里的 `calloc(1,248)` 与其"分配失败即 return 0"的判空**都在且正确**
# (IR 里 `@calloc` 返回值被正常 store 到字段 20),故**不是** calloc 返回 NULL。
# 机理(TPC 侧,只报不改):`TransPyC/lib/core/Handles/HandlesExprAttr.py`
# `_resolve_var_classname`(约 L285-293)对 `rawptr[X]` 形态的模块级全局只看
# `GVar.type.pointee`(= `X*`,仍是指针)⇒ 认不出 `X`,返回 None;兜底的
# `_recover_struct_class`(约 L723-740)虽能多解一层指针,但要求 `X` 已进入
# **函数级**的 `Gen.variables/global_vars`(`HandlesFunctions.py:1848` 每函数清空),
# 而该登记只发生在"裸名 `X` 被求值一次"时(`HandlesExpr.py:479/487`)。
# ⇒ 凡**一上手就直接读写 `_State.<字段>`、且函数内从未单独求值过裸名 `_State`
# 的函数**,`_handle_attr_name` 在 L507-508 返回 None:
# - 作为调用实参 ⇒ 被静默替换为 null(→ memset(null) 崩溃);
# - 作为 `if X == None:` 的条件 ⇒ 整个 `if` 被 `HandlesIf` L170-171 丢弃
# (→ 我们原来写的 `if _State.FrameBuf == None: …` **根本没被生成**)。
# 受影响函数(IR 佐证:这些函数体内 `@_State` 的 load 次数为 0):
# `EntryRva` / `LookupRva` / `EmitFrame` / `WalkViaDbgHelp`。
# 其余函数(`EmitLine` / `DumpMainThread` / `ReadMemCb` / `Install` …)入口都有
# `_State == None` 判空 ⇒ 裸名已登记 ⇒ 字段访问正常(这就是为什么
# `DumpMainThread` 打出的 `frame_buf=1`、`modinited=1` 都是真的)。
# 本模块的修法(**不去改 TPC**):凡直接使用 `_State.<字段>` 的函数,入口
# **第一句就写 `if _State == None: …`**(即使是冗余判空)——它既是真正的兜底,
# 也把裸名 `_State` 登记进 `Gen.variables`,使其后的字段访问被正常解析。
# 崩溃点再加一道局部量兜底:`FBuf: VOIDPTR = _State.FrameBuf` → 判 `FBuf == None`
# → `memset(FBuf, …)`,把"局部变量"这条最可靠的通道用在最危险的一行上。
# B. **0 帧 / DbgHelp 状态共享**:ASan 运行时会自己注册 DbgHelp 符号化器,本模块的
# SymInitialize 因此被拒(DbgHelp 进程上下文是**进程级**状态、按句柄唯一)。但这**不是
# 冲突**:状态是共享的,用同一个 GetCurrentProcess() 伪句柄即可复用其模块表。故 Install
# 的判定改为**取证式**三步:SymInitialize 成功 → SymReady=1;失败 → SymRefreshModuleList
# 刷新共享模块表(官方"外部初始化后再刷新"写法);再统一用 SymGetModuleBase64(主模块)
# 探针裁决——探针为 0 则 SymReady 回退 0 并打印原因,绝不静默 0 帧。回溯路径顺序:
# * 主路径 = `WalkRbpChain`(纯 RBP 链,**零 dbghelp 依赖**);
# * **第二兜底 = `ScanStack` 栈扫描**(零 dbghelp、且**不依赖 RBP / 帧指针**):
# RBP 链只拿到当前 RIP(<=1 帧)时启用(`walk: fallback=stack_scan`),见第十二节;
# * dbghelp 加强:只有 RBP 链拿不到更多帧(<=1)且 DbgUsable != 0(= 探针通过)时才走
# `WalkViaDbgHelp`。它**清空已收帧并整段重建**(顺带丢掉扫描推断帧),是唯一能用 PE
# 展开表(.pdata)穿过**无帧指针的系统帧**、回到 .vp 栈的路径——Ctrl+C 的典型现场
# (主线程阻塞在系统 DLL 里、RBP=0)只有它能还原真实调用链。
# * 降级**必须给原因**(不静默 0 帧):`[diag] symwalk=disabled reason=…`,
# reason ∈ {compile_time_switch, syminitialize_failed_or_main_module_missing,
# callback_missing, null_frame_buf};ASan 探测用 GetModuleHandleA 两个常见
# ASan 运行时名(clang_rt / libclang_rt 前缀),**只用于诊断行**,不再参与裁决。
# `WalkRbpChain` 同步加固(升级为主路径后它必须"绝不把卡死变成崩溃"):
# * 读 [rbp]/[rbp+8] 一律走 `wm.ReadProcessMemory`(**不直接解引用**野 rbp);
# * **每一步无条件打 `[diag] rbp:` 原始读数 + 判定**(先取证后决策,含被拒步;见第十二节);
# * 自洽校验:非 0 / 8 字节对齐 / `[rbp] > rbp` / 单步跨度 <= RBP_MAX_STEP /
# 返回地址落在主模块基址区间 [Base, Base+TRACEBACK_MODULE_MAX) 内才**接受**该帧。
# A. **The crash root cause is not an allocation failure but TPC silently demoting `_State.<field>` to the null constant.**
# Evidence (build-artifact IR; module prefix = first 16 hex digits of this file's sha1, 9b4ee486e08837c5):
# * The source `string.memset(_State.FrameBuf, 0, ...)` was generated as
# `call i8* @memset(i8* null, i32 0, i64 248)` -- the argument is the literal null;
# in the same function `_State.LineBuf` (snprintf), `_State.FuncTableAddr / ModuleBaseAddr`
# (StackWalk64's two callback slots) were likewise all folded to null.
# * `Install`'s `calloc(1,248)` and its null check "return 0 if allocation failed" **are both present and correct**
# (in the IR `@calloc`'s return value is stored to field 20 normally), so it is **not** calloc returning NULL.
# Mechanism (TPC side, report only, no change): `TransPyC/lib/core/Handles/HandlesExprAttr.py`
# `_resolve_var_classname` (approx. L285-293) looks only for a module-level global of the form `rawptr[X]` at
# `GVar.type.pointee` (= `X*`, still a pointer) => it fails to recognize `X` and returns None; the fallback
# `_recover_struct_class` (approx. L723-740) can peel one more pointer level, but requires `X` to already be in
# the **function-level** `Gen.variables/global_vars` (`HandlesFunctions.py:1848` clears it per function),
# and that registration only happens when "the bare name `X` is evaluated once" (`HandlesExpr.py:479/487`).
# => For any function that **reads/writes `_State.<field>` right at the start and never separately evaluates the bare name `_State`
# inside the function**, `_handle_attr_name` returns None at L507-508:
# - as a call argument => silently replaced with null (-> memset(null) crash);
# - as the condition of `if X == None:` => the whole `if` is dropped by `HandlesIf` L170-171
# (-> the `if _State.FrameBuf == None: ...` we originally wrote **was never generated**).
# Affected functions (IR corroboration: these function bodies load `@_State` 0 times):
# `EntryRva` / `LookupRva` / `EmitFrame` / `WalkViaDbgHelp`.
# Other functions (`EmitLine` / `DumpMainThread` / `ReadMemCb` / `Install` ...) all have a
# `_State == None` null check at entry => the bare name is registered => field access works (this is why
# the `frame_buf=1`, `modinited=1` printed by `DumpMainThread` are real).
# This module's fix (**without changing TPC**): every function that uses `_State.<field>` directly gets
# **`if _State == None: ...` as its very first statement** (even if redundant) -- it is both a genuine fallback
# and registers the bare name `_State` into `Gen.variables`, so later field accesses resolve normally.
# At the crash point add one more local-variable fallback: `FBuf: VOIDPTR = _State.FrameBuf` -> check `FBuf == None`
# -> `memset(FBuf, ...)`, using the most reliable channel (a local variable) on the most dangerous line.
# B. **0 frames / DbgHelp state sharing**: the ASan runtime registers its own DbgHelp symbolizer, so this module's
# SymInitialize is rejected (the DbgHelp process context is a **process-level** state, unique per handle). But this is **not
# a conflict**: the state is shared, and using the same GetCurrentProcess() pseudo-handle reuses its module table. So Install's
# decision becomes an **evidence-based** three-step one: SymInitialize succeeds -> SymReady=1; fails -> SymRefreshModuleList
# refreshes the shared module table (the official "refresh after external init" idiom); then a SymGetModuleBase64(main module)
# probe arbitrates -- if the probe is 0, SymReady falls back to 0 and the reason is printed, never a silent 0 frames. Walk-path order:
# * main path = `WalkRbpChain` (pure RBP chain, **zero dbghelp dependency**);
# * **second fallback = `ScanStack` stack scan** (zero dbghelp and **no RBP / frame-pointer dependency**):
# enabled when the RBP chain yields only the current RIP (<=1 frame) (`walk: fallback=stack_scan`), see section 12;
# * dbghelp augmentation: only when the RBP chain yields no more frames (<=1) and DbgUsable != 0 (= probe passed) does it run
# `WalkViaDbgHelp`. It **clears the collected frames and rebuilds the whole stack** (dropping scan-inferred frames too) and is the only path that can use the PE
# unwind table (.pdata) to cross **frame-pointer-less system frames** back to the .vp stack -- the typical Ctrl+C scene
# (main thread blocked in a system DLL, RBP=0) can only have its real call chain recovered by it.
# * A degrade **must give a reason** (no silent 0 frames): `[diag] symwalk=disabled reason=...`,
# reason in {compile_time_switch, syminitialize_failed_or_main_module_missing,
# callback_missing, null_frame_buf}; the ASan probe uses GetModuleHandleA with two common
# ASan runtime names (clang_rt / libclang_rt prefixes), **for diagnostic lines only**, no longer part of the decision.
# `WalkRbpChain` is hardened in step (after becoming the main path it must "never turn a hang into a crash"):
# * reads of [rbp]/[rbp+8] always go through `wm.ReadProcessMemory` (**no direct dereference** of a wild rbp);
# * **every step unconditionally prints `[diag] rbp:` raw readings + verdict** (evidence before decision, including rejected steps; see section 12);
# * self-consistency checks: non-zero / 8-byte aligned / `[rbp] > rbp` / single-step span <= RBP_MAX_STEP /
# return address within the main-module base range [Base, Base+TRACEBACK_MODULE_MAX) -- only then is the frame **accepted**.
#
# -----------------------------------------------------------------------------
# 十二、本轮新增:RBP 链逐步原始读数 + 栈扫描兜底(应答"栈走不长")
# 12. New this round: per-step RBP-chain raw readings + stack-scan fallback (answering "the walk is too short")
# -----------------------------------------------------------------------------
# 现场(用户实测,`#0` 已正确解析:RIP 在主模块、映射/查表/帧 #0 全对):`#1 = 0x121fa`
# ——远小于主模块基址、也不是栈地址 ⇒ 链在第 1 步即断。⇒ 先把"断在哪一步、为什么断"
# 变成可读数据,再补一条**不依赖 RBP**的路径。
# A. **逐步原始读数**(`WalkRbpChain` 内,前缀 `[diag] rbp:`,**先于任何 reject 早退**,
# TRACEBACK_DIAG=1 时才输出):
# frame=<i> rbp=0x… [rbp]=0x… [rbp+8]=0x… ← 读到的原始 8 字节值
# frame=<i> rpm_ok=<0/1> rpm_err=<GetLastError> accept=<0/1> reject_reason=<…>
# reject_reason ∈ {rbp_zero, not_aligned, rpm_failed, ret_zero, ret_out_of_module,
# not_growing, step_too_large};被接受时 reason=`accept`、accept=1。
# 仅被接受的步才 FramePush 入帧缓冲(旧实现在校验前就输出,会把 `0x121fa` 这类垃圾值当成
# 一帧 external 打出来,误导读者)。三种断链根因据此一眼可分:读失败 / 值本身不是返回
# 地址(帧指针未保留)/ 被自洽校验档掉。
# B. **栈扫描兜底**(`ScanStack`):RBP 链 <=1 帧时启用(`walk: fallback=stack_scan`)。
# 在 [RSP, RSP+2MiB) 内按 8 字节步长遍历,**4 KiB/块整块读**(失败即停并打地址/err);
# 候选 = 落在主模块区间、且**相邻 8 字节字(A-8 或 A+8)像栈地址**的字;按值去重、
# 上限 64(诊断打 `scan_candidates=<N>`);推断帧入缓冲时带 Scanned=1,打印时在帧行末尾加
# ` [inferred]`(` File "…", line 0, in <unknown> [inferred]`)明确标注**扫描推断**
# (可含误报,不与权威帧混淆,见第十三节)。全部读取只经 ReadProcessMemory,
# 绝不直接解引用主线程内存(诊断设施不许把"卡死"变成"崩溃")。
# C. **帧指针是否保留**(调查结论与 TPC 侧修法落点,本模块只报不改):
# * 单一 llc 调用点 = `TransPyC/lib/Projectrans/Phase2Translator.py:2160 _llc_compile`,
# 它在 `--traceback` 打开时把 `-frame-pointer=all` 追加到命令(2187-2193);
# App 模块(如 `TransPyV/App/lib/core/Gen/Expr/ExprGenOps.vp`,模块 sha1
# `8a96a2885bae09ca`)走的是**同一个**调用点(2608 的 _dso.ll / 2319 的内联合并),
# 故 flag 的落点本身没问题。
# * 缓存失效:App 模块 obj = `output/{sha1}.obj`,`_compile_ll_files` 见其存在即跳过
# (2621),但 `--traceback` 时 `hotspot_on`(1144)为真 ⇒ 1196-1204 会删除旧 obj
# 强制重编;include 模块另有隔离目录 `includes.binary_traceback`(3097-3099)+
# 旧 obj 删除(3181-3190)。⇒ App/include 都不会静默复用"无帧指针的旧 obj"。
# * 交付结论:若 `[diag] rbp:` 显示 `[rbp+8]` 仍不是返回地址(`ret_out_of_module`),
# 则说明 `-frame-pointer=all`(llc 默认值级别的开关)没有生效,最稳的改法是在**IR 里
# 按函数显式标注** `"frame-pointer"="all"` —— 落点
# `TransPyV/App/lib/core/Gen/Func/FuncGenBase.vp`(属性拼装 `_append_llvm_attr`,154-181)
# 与 `TransPyC/lib/core/Handles/HandlesFunctions.py:1735`(`func.attributes.add('noredzone')`
# 旁)—— 让帧指针成为**函数属性**而非 llc 命令行默认值,从根上免疫"llc 是否采纳默认值"。
# * 【补充实测,Test/LLvmLiteTest Ctrl+C】`RBP=0x0`、RIP 落在系统 DLL:拒绝原因是
# `rbp_zero`(不是 `ret_out_of_module`)⇒ 断链点**不在 .vp 函数**,而在被中断的那一层
# 系统帧。RBP 是 callee-saved,但系统/CRT 的免帧指针代码会把它当**临时寄存器**用
# (`xor ebp,ebp` 是常见的清零写法),故中断现场的 RBP 与"最近一个维护帧指针的祖先"
# 无关。⇒ 结论:**给 .vp 函数保留帧指针无法救这条路**(链在更内层就已断),只有基于
# PE 展开表(.pdata)的回溯(dbghelp!StackWalk64,见第十一节 B)才能从这种现场还原
# 真实调用链。帧指针开关因此**不是**本轮的修法落点,保留在上仅作历史记录。
# Live case (user measurement, `#0` resolved correctly: RIP in the main module, mapping/lookup/frame #0 all correct): `#1 = 0x121fa`
# -- far below the main-module base and not a stack address => the chain breaks at step 1. => First turn "where it broke and why"
# into readable data, then add a path that **does not depend on RBP**.
# A. **Per-step raw readings** (inside `WalkRbpChain`, prefix `[diag] rbp:`, **before any reject early-return**,
# output only when TRACEBACK_DIAG=1):
# frame=<i> rbp=0x... [rbp]=0x... [rbp+8]=0x... <- raw 8-byte values read
# frame=<i> rpm_ok=<0/1> rpm_err=<GetLastError> accept=<0/1> reject_reason=<...>
# reject_reason in {rbp_zero, not_aligned, rpm_failed, ret_zero, ret_out_of_module,
# not_growing, step_too_large}; when accepted, reason=`accept`, accept=1.
# Only accepted steps are FramePush-ed into the frame buffer (the old implementation output before validating, printing garbage values like `0x121fa`
# as an external frame, misleading the reader). The three chain-break root causes are distinguishable at a glance: read failure / the value itself is not a return
# address (frame pointer not preserved) / rejected by the self-consistency checks.
# B. **Stack-scan fallback** (`ScanStack`): enabled when the RBP chain yields <=1 frame (`walk: fallback=stack_scan`).
# Scans [RSP, RSP+2MiB) in 8-byte steps, **reading 4 KiB per chunk whole** (stops on failure and prints address/err);
# a candidate = a word within the main-module range whose **adjacent 8-byte word (A-8 or A+8) looks like a stack address**; de-duplicated by value,
# capped at 64 (diagnostic prints `scan_candidates=<N>`); inferred frames enter the buffer with Scanned=1 and append
# ` [inferred]` (` File "...", line 0, in <unknown> [inferred]`) to the frame line to mark them clearly as **scan inferences**
# (may contain false positives; never confused with authoritative frames; see section 13). All reads go only through ReadProcessMemory,
# never a direct dereference of main-thread memory (a diagnostic facility must not turn a "hang" into a "crash").
# C. **Whether the frame pointer is preserved** (investigation conclusion and TPC-side fix location; this module reports only, no change):
# * The single llc call site = `TransPyC/lib/Projectrans/Phase2Translator.py:2160 _llc_compile`,
# which appends `-frame-pointer=all` to the command when `--traceback` is on (2187-2193);
# the App module (e.g. `TransPyV/App/lib/core/Gen/Expr/ExprGenOps.vp`, module sha1
# `8a96a2885bae09ca`) goes through the **same** call site (2608's _dso.ll / 2319's inlining merge),
# so the flag's location itself is fine.
# * Cache invalidation: App module obj = `output/{sha1}.obj`, `_compile_ll_files` skips it if it exists
# (2621), but with `--traceback` `hotspot_on` (1144) is true => 1196-1204 deletes the old obj
# to force recompilation; the include module has a separate isolated dir `includes.binary_traceback` (3097-3099) +
# old-obj deletion (3181-3190). => Neither App nor include silently reuses a "frame-pointer-less old obj".
# * Delivery conclusion: if `[diag] rbp:` shows `[rbp+8]` is still not a return address (`ret_out_of_module`),
# then `-frame-pointer=all` (a flag at the level of an llc default) did not take effect; the most robust fix is to annotate
# `"frame-pointer"="all"` explicitly per function **in the IR** -- location
# `TransPyV/App/lib/core/Gen/Func/FuncGenBase.vp` (attribute assembly `_append_llvm_attr`, 154-181)
# and `TransPyC/lib/core/Handles/HandlesFunctions.py:1735` (next to `func.attributes.add('noredzone')`
# ) -- making the frame pointer a **function attribute** rather than an llc command-line default, immune at the root to "whether llc honors the default".
# * [Extra measurement, Test/LLvmLiteTest Ctrl+C] `RBP=0x0`, RIP in a system DLL: the reject reason is
# `rbp_zero` (not `ret_out_of_module`) => the break is **not in a .vp function** but in the interrupted
# system frame. RBP is callee-saved, but frame-pointer-less system/CRT code uses it as a **scratch register**
# (`xor ebp,ebp` is a common zeroing idiom), so the interrupted site's RBP is unrelated to "the nearest ancestor
# that maintains a frame pointer". => Conclusion: **keeping a frame pointer for .vp functions cannot save this path** (the chain is already broken further in); only
# PE unwind-table (.pdata) based walking (dbghelp!StackWalk64, see section 11 B) can recover
# the real call chain from such a site. The frame-pointer switch is therefore **not** this round's fix location and is kept above only as a historical record.
#
# -----------------------------------------------------------------------------
# 十三、本轮:真 Python traceback 风格输出 + TRACEBACK_DIAG 开关
# 13. This round: real Python-traceback-style output + the TRACEBACK_DIAG switch
# -----------------------------------------------------------------------------
# A. 输出形状(默认,TRACEBACK_DIAG 未开)——整段 traceback 形如:
# A. Output shape (default, TRACEBACK_DIAG off) -- the whole traceback looks like:
# Traceback (most recent call last):
# File "D:\...\App\main.vp", line 238, in main
# w_upper: str = w_str.upper()
# File "<0x7ff6... +0x1a2b no source line mapping>", line 0, in <unknown>
# File "<0x7ffb... external>", line 0, in <unknown> [inferred]
# * **帧序反过来了**:回溯从最内层(当前 RIP)向调用者走,收集顺序 = 从内到外,打印时
# 逆序 ⇒ **最外层(CRT/调用者)在前、最内层最后**(与 Python traceback 一致)。故必须
# 先把帧收集进缓冲(FramePush),走栈结束后再逆序打印(DumpMainThread)。
# * 命中源位置的帧打两行:第一行 2 空格缩进 `File "路径", line N, in 函数名`(函数名取不到
# 写 `<unknown>`);第二行 4 空格缩进 + 该行源码——**运行期重开该文件**顺序读第 N 行
# (去行首尾空白、超长按 TRACEBACK_LINE_CAP 截断);**打不开 / 行号越界 / 读失败则整行
# 不打**(无占位、无报错,静默跳过),实现见 EmitSourceText(栈上缓冲,回调线程零堆分配)。
# * ScanStack 推断帧在帧行末尾加 ` [inferred]`(保留"可能是误报"的信息,不再用 `~` 前缀)。
# * 未命中映射的帧 → `File "<0x<地址> +0x<rva> no source line>", line 0, in <unknown>`
# (地址+RVA 保留在同一行);落在主模块之外的帧 → `File "<0x<地址> external>", line 0, in <unknown>`。
# B. 帧缓冲:TracebackState.FrameAddr / FrameScan(`t.CArray[uint64/int32, TRACEBACK_FRAME_BUF]`,
# `TRACEBACK_FRAME_BUF = TRACEBACK_MAX_FRAMES + TRACEBACK_SCAN_MAX_HITS = 128`),
# **内嵌于状态对象** ⇒ 由 Install 的 `TracebackState.__sizeof__()` 分配覆盖,无需独立 malloc、
# 回调线程零分配。缓冲满时新帧直接丢弃(不越界),最终 `frames=` 以实际收集数为准。
# C. **TRACEBACK_DIAG 开关**:读环境变量 `TRACEBACK_DIAG`,值 == "1" 才开;**每进程只读一次
# 并缓存**(_DiagFlag -1/0/1,见 DiagEnabled)。关闭(默认)时不输出任何 `[diag] ...` /
# `srcline map: ...` 行,也不加 ` [dump#<k>] ` 行号前缀(见第十节 B),使 dump 成为干净的
# traceback;打开时诊断信息量与既有一致(含逐帧 `[diag] rbp:` / `[diag] scan:` / symwalk 行)。
# 取值 API = kernel32!GetEnvironmentVariableA(本模块顶部自声明,见第八节)。
# D. 转储序号与落文件提示移到整段 traceback **之后**(` [traceback] dump seq=… also appended
# to <exe>.stackdump.txt` 行),不再插在帧中间;`Ctrl+C` 转储后返回 FALSE(进程退出)、
# `Ctrl+Break` 转储后返回 TRUE(继续运行)语义不变。
# * **Frame order is reversed**: the walk goes from the innermost (current RIP) outward to callers, so collection order = inner to outer, and printing reverses it
# => **the outermost (CRT/caller) first, the innermost last** (matching Python traceback). So frames must
# first be collected into a buffer (FramePush), then printed in reverse after the walk ends (DumpMainThread).
# * A frame that hits a source location prints two lines: the first is 2-space-indented `File "path", line N, in function` (function name
# is `<unknown>` if unavailable); the second is 4-space-indented + that source line -- **the file is reopened at runtime** and line N read sequentially
# (leading/trailing whitespace stripped, over-long truncated to TRACEBACK_LINE_CAP); **if it cannot be opened / the line number is out of range / the read fails, the whole line is
# omitted** (no placeholder, no error, silently skipped); see EmitSourceText (stack buffer, zero heap allocation from the callback thread).
# * A ScanStack inferred frame appends ` [inferred]` to the frame line (keeping "may be a false positive" information, no longer a `~` prefix).
# * A frame missing from the mapping -> `File "<0x<address> +0x<rva> no source line>", line 0, in <unknown>`
# (address+RVA kept on the same line); a frame below the main module -> `File "<0x<address> external>", line 0, in <unknown>`.
# B. Frame buffer: TracebackState.FrameAddr / FrameScan (`t.CArray[uint64/int32, TRACEBACK_FRAME_BUF]`,
# `TRACEBACK_FRAME_BUF = TRACEBACK_MAX_FRAMES + TRACEBACK_SCAN_MAX_HITS = 128`),
# **inlined into the state object** => covered by Install's `TracebackState.__sizeof__()` allocation, no separate malloc,
# zero allocation from the callback thread. When full, new frames are simply dropped (no overflow); the final `frames=` reflects the actual collected count.
# C. **TRACEBACK_DIAG switch**: reads env var `TRACEBACK_DIAG`, enabled only when the value == "1"; **read once per process
# and cached** (_DiagFlag -1/0/1, see DiagEnabled). When off (default) it emits no `[diag] ...` /
# `srcline map: ...` lines and adds no ` [dump#<k>] ` line-number prefix (see section 10 B), making the dump a clean
# traceback; when on, the diagnostic detail matches the previous amount (including per-frame `[diag] rbp:` / `[diag] scan:` / symwalk lines).
# The read API = kernel32!GetEnvironmentVariableA (declared at the top of this module, see section 8).
# D. The dump sequence number and the file-output notice move to **after** the whole traceback (
# ` [traceback] dump seq=... also appended to <exe>.stackdump.txt`), no longer inserted between frames; `Ctrl+C` dumping returns FALSE (process exits),
# `Ctrl+Break` dumping returns TRUE (keeps running) -- semantics unchanged.
#
# -----------------------------------------------------------------------------
# 十四、崩溃转储(未处理异常;应答"崩溃无错误堆栈")
# 14. Crash dump (unhandled exception; answering "crash has no error stack")
# -----------------------------------------------------------------------------
# A. 动机:Ctrl+C 有栈,**崩溃没有**——ASan 一旦过了它的报告窗口就什么都不打印,OS 默认只留一个
# 退出码(同一处崩溃实测:开 ASan = 0x80000003 STATUS_BREAKPOINT(int3),关 ASan =
# 0xC0000005 ACCESS_VIOLATION,两者都**没有堆栈**)。故 Install 追加注册
# kernel32!SetUnhandledExceptionFilter(CrashHandler),由 CrashHandler → DumpCrashThread
# 在**出错线程**上补出这条缺失的栈。
# B. DumpCrashThread 的三条纪律:
# * **绝不吞异常**:过滤器恒定返回 EXCEPTION_CONTINUE_SEARCH(0),退出码 / WER / ASan 报告
# 的行为原样保留,本模块只加堆栈;
# * **不解引用 OS 指针**:EXCEPTION_POINTERS({ExceptionRecord, ContextRecord})与
# EXCEPTION_RECORD 头部(u32 ExceptionCode / u32[…] ExceptionAddress)一律经
# ReadProcessMemory 读取;
# * **不写 OS 的 CONTEXT**:StackWalk64 每走一帧都会回写它遍历的那条记录,故先把 OS 给的
# CONTEXT 拷进 _State.ContextBuf(尺寸已与 winnt.h 对齐 0x4D0)再从副本走栈。
# C. 走栈顺序与打印:dbghelp 优先(崩溃点通常在系统 DLL 里,RBP 链必然无从下手)→ RBP 链 →
# 栈扫描,与 Ctrl+C 路径同一套 WalkViaDbgHelp / WalkRbpChain / ScanStack 与 PrintFrames;
# 与 Ctrl+C 转储**共用同一把重入锁**(AtomicTryEnterDump),故一次只可能有一份 dump:
# 崩溃若发生在转储进行中,本次崩溃不转储(锁被占),避免二次伤害。
# 输出形状:`=== TPC crash dump (unhandled exception; the process keeps terminating) ===`
# → ` Trigger=unhandled-exception code=0x… addr=0x… av_kind=… av_addr=0x… crashthread=…`
# → ` exe base=… RIP=… RSP=… RBP=…` → ` regs: rcx=… rdx=… r8=… r9=… r12=…`
# → 与 Ctrl+C 逐字同形的 Traceback 帧(含源码行回读)→ ` frames=N` → 落文件提示
# → `=== crash dump end ===`。
# * `av_kind`/`av_addr` 取自 EXCEPTION_RECORD 的 ExceptionInformation[0/1](0=读 1=写 8=执行;
# 数据地址),只在 0xC0000005 类访问违规时有效——崩溃现场正是"原始数字胜过猜测"的场合;
# * `regs` 给的是**出错帧的实参现场**(Win64 前 4 个整型实参 = rcx/rdx/r8/r9;r12 是该帧
# 用来解引用指针的寄存器):一条就能区分「实参传错」与「实参对、指针所指内容错」,
# 不必再来一轮重编。Ctrl+C 路径不打印它(那里没有"出错指令的算子"可看)。
# * **不打印 `[RBP+8]` 当返回地址**:.vp 帧的 RBP 是 locals base(`leaq N(%rsp), %rbp`),
# 返回地址在 RBP + 帧字节数 - 8(逐函数不同;实测 _TypePrintDepth 是 RBP+0x38),
# `[RBP+8]` 只是无关局部量。调用者一律以 .stackmap 走栈结果为准。
# 中文件头(栈转储/触发=)只属于 Ctrl+C 路径,崩溃路径不动它。
# D. 已知边界:ASan 的 VEH 优先于未处理异常过滤器——若某次崩溃由 ASan 正常报告(它自带堆栈),
# 本路径不会被调用;本路径覆盖的正是「ASan 报不出来 / 没开 ASan」那两类(实测的两个退出码
# 都在其中)。栈耗尽类崩溃(本路径自身也需要栈)不在保障范围内。
# A. Motivation: Ctrl+C has a stack, **a crash does not** -- once ASan passes its report window it prints nothing, and the OS leaves only
# an exit code (measurements of the same crash: ASan on = 0x80000003 STATUS_BREAKPOINT(int3), ASan off =
# 0xC0000005 ACCESS_VIOLATION; neither has **a stack**). So Install additionally registers
# kernel32!SetUnhandledExceptionFilter(CrashHandler), and CrashHandler -> DumpCrashThread
# produces the missing stack on the **faulting thread**.
# B. DumpCrashThread's three rules:
# * **Never swallow the exception**: the filter always returns EXCEPTION_CONTINUE_SEARCH(0); exit code / WER / ASan report
# behavior is preserved; this module only adds the stack;
# * **Never dereference OS pointers**: EXCEPTION_POINTERS ({ExceptionRecord, ContextRecord}) and
# the EXCEPTION_RECORD header (u32 ExceptionCode / u32[...] ExceptionAddress) are always read via
# ReadProcessMemory;
# * **Never write the OS's CONTEXT**: StackWalk64 writes back to the record it walks on every frame, so first copy the OS-provided
# CONTEXT into _State.ContextBuf (size already aligned with winnt.h, 0x4D0) and walk from the copy.
# C. Walk order and printing: dbghelp first (the crash point is usually in a system DLL, where the RBP chain is hopeless) -> RBP chain ->
# stack scan, the same WalkViaDbgHelp / WalkRbpChain / ScanStack and PrintFrames as the Ctrl+C path;
# it **shares the same reentrancy lock** with the Ctrl+C dump (AtomicTryEnterDump), so only one dump can exist at a time:
# if a crash happens while a dump is in progress, this crash is not dumped (lock held), avoiding a second hit.
# Output shape: `=== TPC crash dump (unhandled exception; the process keeps terminating) ===`
# -> ` Trigger=unhandled-exception code=0x... addr=0x... av_kind=... av_addr=0x... crashthread=...`
# -> ` exe base=... RIP=... RSP=... RBP=...` -> ` regs: rcx=... rdx=... r8=... r9=... r12=...`
# -> Traceback frames word-for-word identical to Ctrl+C (including source-line re-read) -> ` frames=N` -> file-output notice
# -> `=== crash dump end ===`.
# * `av_kind`/`av_addr` come from EXCEPTION_RECORD's ExceptionInformation[0/1] (0=read 1=write 8=execute;
# data address) and are valid only for 0xC0000005 access-violation-type faults -- a crash site is exactly where "raw numbers beat guessing";
# * `regs` gives **the argument state of the faulting frame** (Win64's first 4 integer arguments = rcx/rdx/r8/r9; r12 is the register that frame
# uses to dereference a pointer): one line distinguishes "wrong argument passed" from "argument correct, pointed-to content wrong",
# with no need for another rebuild. The Ctrl+C path does not print it (there is no "operand of the faulting instruction" to see there).
# * **Does not print `[RBP+8]` as a return address**: a .vp frame's RBP is a locals base (`leaq N(%rsp), %rbp`),
# and the return address is at RBP + frame size - 8 (varies per function; measured _TypePrintDepth is RBP+0x38),
# `[RBP+8]` is just an unrelated local. Callers always rely on the .stackmap walk result.
# The Chinese file header (stack dump/trigger=) belongs only to the Ctrl+C path; the crash path leaves it alone.
# D. Known boundaries: ASan's VEH takes priority over the unhandled-exception filter -- if a crash is reported normally by ASan (which has its own stack),
# this path is not called; this path covers exactly the two cases where "ASan cannot report it / ASan is off" (the two measured exit codes
# are both among them). Stack-exhaustion crashes (this path itself needs stack) are out of scope.
# =============================================================================
# This module needs kernel32!GetEnvironmentVariableA for the TRACEBACK_DIAG switch, but
# includes/w32/win32base.vp does not declare it (the repository declares it only in
+3 -3
View File
@@ -1,8 +1,8 @@
# ============================================================
# vqt6 — Viper 的 Qt6 绑定包
# vqt6 - Viper's Qt6 binding package
#
# 公共 Pythonic API(基于 C 桥接层)
# 用户用法:
# Public Pythonic API (built on the C bridge layer)
# Usage:
# import vqt6
# app = vqt6.QApplication([])
# win = vqt6.QWidget()
+3 -3
View File
@@ -3,10 +3,10 @@ from stdint import *
from vqt6._types import *
# ============================================================
# vqt6/_qtcore.py — QtCore 的 t.State FFI 声明
# vqt6/_qtcore.py - t.State FFI declarations for QtCore
#
# 所有函数都是 C 桥接层 (libvqt6_bridge.a) 的 extern "C" 函数
# 命名规范: vqt6_<class>_<action>
# All functions are extern "C" functions of the C bridge layer (libvqt6_bridge.a)
# Naming convention: vqt6_<class>_<action>
# ============================================================
# ============================================================
+1 -1
View File
@@ -3,7 +3,7 @@ from stdint import *
from vqt6._types import *
# ============================================================
# vqt6/_qtgui.py — QtGui 的 t.State FFI 声明
# vqt6/_qtgui.py - t.State FFI declarations for QtGui
# ============================================================
# ============================================================
+1 -1
View File
@@ -3,7 +3,7 @@ from stdint import *
from vqt6._types import *
# ============================================================
# vqt6/_qtnetwork.py — QtNetwork 的 t.State FFI 声明
# vqt6/_qtnetwork.py - t.State FFI declarations for QtNetwork
# ============================================================
# ============================================================
+1 -1
View File
@@ -3,7 +3,7 @@ from stdint import *
from vqt6._types import *
# ============================================================
# vqt6/_qtwidgets.py — QtWidgets 的 t.State FFI 声明
# vqt6/_qtwidgets.py - t.State FFI declarations for QtWidgets
# ============================================================
# ============================================================
+3 -3
View File
@@ -2,10 +2,10 @@ import t
from stdint import *
# ============================================================
# vqt6/_types.py — 公共类型定义
# vqt6/_types.py - common type definitions
#
# Qt6 C++ 类在 Viper 端都是不透明指针 (void*)
# 不暴露 C++ 类的内部布局,只暴露指针
# Qt6 C++ classes are all opaque pointers (void*) on the Viper side
# The internal layout of C++ classes is not exposed, only pointers are
# ============================================================
# Opaque pointer typedefs (Viper-side representation of Qt6 C++ classes)
+23 -23
View File
@@ -3,33 +3,33 @@ from stdint import *
from w32.win32base import *
# =============================================================================
# dbghelp / StackWalk64 声明(供 includes/traceback.vp 的 Ctrl+C 栈转储使用)
# dbghelp / StackWalk64 declarations (used by the Ctrl+C stack dump in includes/traceback.vp)
#
# 声明本仓用到的 dbghelp 栈回溯 API 与三个栈帧结构体:
# SymInitialize 初始化 dbghelp 的进程上下文(fInvadeProcess=TRUE 只枚举模块、
# 配合 SYMOPT_DEFERRED_LOADS 不加载 PDB)
# SymRefreshModuleList 本进程的 DbgHelp 已被其它组件初始化(本模块的 SymInitialize 被拒)
# 时,按其既有进程上下文重新枚举模块表("外部初始化后再刷新")
# SymSetOptions 设定 SymInitialize 之后固定的选项集
# SymFunctionTableAccess64 按 RIP 查 RUNTIME_FUNCTION(PE 异常目录;不需要调试符号)
# SymGetModuleBase64 按 RIP 取模块基址
# StackWalk64 逐帧回溯(MachineType=IMAGE_FILE_MACHINE_AMD64)
# Declares the dbghelp stack-walking APIs used by this repo plus three stack frame structs:
# SymInitialize initializes the dbghelp process context (fInvadeProcess=TRUE enumerates modules only,
# combined with SYMOPT_DEFERRED_LOADS it does not load PDBs)
# SymRefreshModuleList when this process's DbgHelp was already initialized by another component (this module's SymInitialize is refused),
# re-enumerates the module table using its existing process context ("refresh after external init")
# SymSetOptions sets the option set fixed by SymInitialize
# SymFunctionTableAccess64 looks up the RUNTIME_FUNCTION by RIP (PE exception directory; does not need debug symbols)
# SymGetModuleBase64 gets the module base address by RIP
# StackWalk64 walks frame by frame (MachineType=IMAGE_FILE_MACHINE_AMD64)
#
# 其中 SymInitialize / SymSetOptions / StackWalk64 由 traceback.vp **直接调用**(静态引用);
# SymFunctionTableAccess64 / SymGetModuleBase64 既作为 StackWalk64 的回调原型在此登记,也被
# traceback.vp **直接调用**做首帧取证诊断(首帧返回码/AddrPC/SymFunctionTableAccess64 返回值,
# 用于判定 StackWalk64 "0 帧"的根因)——同样属静态引用。traceback.vp 仍用 GetProcAddress 取它们
# 的地址以填入 StackWalk64 的回调槽,为的是避开「跨模块函数名 → 函数指针」的隐式转换
# (该动态解析写法见 VET/App/lib/VGuard.vp:852-867)。
# Among these, SymInitialize / SymSetOptions / StackWalk64 are **called directly** by traceback.vp (static references);
# SymFunctionTableAccess64 / SymGetModuleBase64 are both registered here as StackWalk64 callback prototypes and
# **called directly** by traceback.vp for first-frame forensic diagnostics (first-frame return code / AddrPC / SymFunctionTableAccess64 return value,
# used to determine the root cause of StackWalk64 "0 frames") -- also static references. traceback.vp still uses GetProcAddress to get their
# addresses to fill StackWalk64's callback slots, in order to avoid the implicit conversion of "cross-module function name -> function pointer"
# (see VET/App/lib/VGuard.vp:852-867 for that dynamic-resolution idiom).
#
# 链接期依赖:dbghelp.lib(MinGW: libdbghelp.a,-ldbghelp);ReadProcessMemory 在
# kernel32(includes/w32/win32memory.vp:72)。本文件不引入其它链接期符号。
# Link-time dependencies: dbghelp.lib (MinGW: libdbghelp.a, -ldbghelp); ReadProcessMemory is in
# kernel32 (includes/w32/win32memory.vp:72). This file introduces no other link-time symbols.
#
# 结构体布局(x64,自然对齐;逐字段核对 winnt.h / dbghelp.h):
# ADDRESS64 0x00 Offset(u64) 0x08 Segment(u16) 0x0C Mode(enum=i32) → 16 字节
# KDHELP64 96 字节(见字段注释)
# STACKFRAME64 248 字节(见字段注释)
# 三者只用于传给 StackWalk64(它按自身定义写入),故尺寸/偏移必须与 C 侧逐字节一致。
# Struct layouts (x64, natural alignment; verified field by field against winnt.h / dbghelp.h):
# ADDRESS64 0x00 Offset(u64) 0x08 Segment(u16) 0x0C Mode(enum=i32) -> 16 bytes
# KDHELP64 96 bytes (see field comments)
# STACKFRAME64 248 bytes (see field comments)
# All three are only passed to StackWalk64 (which writes per its own definition), so sizes/offsets must match the C side byte for byte.
# =============================================================================
# IMAGE_FILE_MACHINE_* (winnt.h; x64 = 0x8664)
+1 -1
View File
@@ -4,7 +4,7 @@ from w32.win32base import *
# ============================================================
# w32/winsock2.py — WinSock2 API 声明 (ws2_32.dll)
# w32/winsock2.py - WinSock2 API declarations (ws2_32.dll)
# ============================================================
# Version constants
+10 -10
View File
@@ -1,23 +1,23 @@
# -*- coding: utf-8 -*-
# zipfile.vp — ZIP 归档读写模块(Python zipfile 子集)
# zipfile.vp - ZIP archive read/write module (subset of Python zipfile)
#
# 支持:
# - 写入成员: ZIP_STORED / ZIP_DEFLATED(raw deflate,wbits=-15,CRC32 校验)
# - 读取成员: 自动识别 ZIP_STORED / ZIP_DEFLATED,解压后做 CRC32 校验
# - 读取时从文件尾部回扫 EOCD(容忍注释/前导数据)
# Supports:
# - writing members: ZIP_STORED / ZIP_DEFLATED (raw deflate, wbits=-15, CRC32 check)
# - reading members: auto-detects ZIP_STORED / ZIP_DEFLATED, checks CRC32 after decompression
# - on read, scans back from the end of the file for EOCD (tolerates a comment/leading data)
#
# 不支持: zip64(>4GB/成员数>65535)、加密、分卷、数据描述符(flag bit 3)等。
# Not supported: zip64 (>4GB / more than 65535 members), encryption, spanning, data descriptors (flag bit 3), etc.
#
# 成员名为原始 UTF-8 字节(写入时置 flag bit 11,读取时不自动解码)。
# Member names are raw UTF-8 bytes (flag bit 11 is set when writing, not auto-decoded when reading).
#
# 用法(对齐 Python 语义,只读模式自动把整个 zip 读入内存):
# Usage (aligned with Python semantics; read-only mode reads the whole zip into memory automatically):
# w: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "w")
# w.write("a.txt", buf, buf_len) # 默认 ZIP_DEFLATED
# w.write("a.txt", buf, buf_len) # defaults to ZIP_DEFLATED
# w.close()
#
# r: zipfile.ZipFile* = zipfile.ZipFile("out.zip", "r")
# olen: sizet = 0
# data: bytes = r.read("a.txt", &olen) # malloc 缓冲,用后 free
# data: bytes = r.read("a.txt", &olen) # malloc buffer, free after use
# err = zipfile.error_code() / zipfile.error()
import stdlib
import string