feat(vqt6/vipermath/vipersimd/w32/requests): 自绘 Canvas/Graph3D/ChartView 与 Qt6 Charts、新增 gzip 与 torch-lite 模块, 修包装类悬垂指针与中文路径/exp/大文件下载

- vqt6: 包装类统一补 __new__ 堆分配, 修"创建函数栈帧 alloca"被存进字段/跨帧返回后 _ptr 悬垂(传给 QLayout::addWidget 等即崩溃); 菜单类按依赖顺序前移, menuBar()/statusBar() 改返回裸指针; 接入 Qt6 Charts 并新增 _bridge_canvas(Canvas 手绘板 / Graph3D 软件 3D 网络图 / ChartView 折线图); 新增自绘 QRadioButton(圆环+内点, 过渡动画)、剪贴板、应用级字体族 API
- 新增模块: gzip.vp(RFC1952/1951 解码器)、torch/(torch-lite 稠密张量 + conv/pool 前反向)
- vipersimd: 新增 scalar_fma/add/mul/sqrt_array 未向量化对照内核(与对应 SIMD 版逐位一致)
- w32: 新增 UTF-8/UTF-16 路径转换, CreateFile/CreateDirectory 走 W 系列, 修中文路径被按 GBK 误读; read_all 参数 max_count -> buf_size 并文档化"容量含终止符"契约
- requests: https 响应缓冲按需翻倍扩容(上限 256MB), 修 MB 级正文(如 MNIST .gz)被 4KB 静默截断
- vipermath: 重写 exp 为范围归约 + 短泰勒, 修负值发散(x=-30 得 3.7e10)与 NaN 触发 2^31 次缩放导致的卡死
- builtins: 新增 sizet/uzt 别名; stdarg: va_arg 参数 CType -> CPtr
- chore: 删除 vendored jpeg change.log
This commit is contained in:
TermiNexus committed 2026-09-10 23:09:05 +08:00
1 parent 7d092f25b1
commit 728d090815
22 files changed
+3915 -755

No files matched your search

+2
View File
@@ -28,6 +28,8 @@ def max(a: double, b: double) -> double:
# rowptr 已内置于编译器(TPC/TPV 双端),作为语法保留字,不可重定义:
# rowptr[T] 固定表示"裸指针,指向原始类型 T"(等价 t.CPtr[T])。
size_t: t.CTypedef = t.CSizeT
uzt: t.CTypedef = t.CSizeT
sizet: t.CTypedef = t.CSizeT
int8: t.CTypedef = t.CInt8T
int16: t.CTypedef = t.CInt16T
int32: t.CTypedef = t.CInt32T
-527
View File
@@ -1,527 +0,0 @@
CHANGE LOG for Independent JPEG Group's JPEG software
Version 9f 14-Jan-2024
-----------------------
Add build system for C++Builder/RAD Studio.
Add build system for Xcode (beside configure).
Add ARM64EC (Emulation Compatible) platform support in the
Visual Studio build.
Version 9e 16-Jan-2022
-----------------------
Include alternate user interface files for cjpeg/djpeg.
jcparam.c: change default chrominance DC quantization factor
for lossless support. Note: Requires rebuild of test images.
rdgif.c, cderror.h: add sanity check for GIF image dimensions.
Thank to Casper Sun for cjpeg potential vulnerability report.
Add ARM and ARM64 platform support in the Visual Studio build.
Version 9d 12-Jan-2020
-----------------------
Optimize the optimal Huffman code table generation to produce
slightly smaller files. Thank to John Korejwa for suggestion.
Note: Requires rebuild of testimgp.jpg.
Decoding Huffman: Use default tables if tables are not defined.
Thank to Simone Azzalin for report (Motion JPEG),
and to Martin Strunz for hint.
Add sanity check in optimal Huffman code table generation.
Thank to Adam Farley for suggestion.
rdtarga.c: use read_byte(), with EOF check, instead of getc()
in read_*_pixel().
Thank to Chijin Zhou for cjpeg potential vulnerability report.
jmemnobs.c: respect the max_memory_to_use setting in
jpeg_mem_available() computation. Thank to Sheng Shu and
Dongdong She for djpeg potential vulnerability report.
jdarith.c, jdhuff.c: avoid left shift of negative value
compiler warning in decode_mcu_AC_refine().
Thank to Indu Bhagat for suggestion.
Add x64 (64-bit) platform support, avoid compiler warnings.
Thank to Jonathan Potter, Feiyun Wang, and Sheng Shu for suggestion.
Adjust libjpeg version specification for pkg-config file.
Thank to Chen Chen for suggestion.
Restore GIF read and write support from libjpeg version 6a.
Thank to Wolfgang Werner (W.W.) Heinz for suggestion.
Improve consistency in raw (downsampled) image data processing mode.
Thank to Zhongyuan Zhou for hint.
Avoid out of bounds array read (AC derived table pointers)
in start pass in jdhuff.c. Thank to Peng Li for report.
Improve code sanity (jdhuff.c).
Thank to Reza Mirzazade farkhani for reports.
Add jpegtran -drop option; add options to the crop extension and wipe
to fill the extra area with content from the source image region,
instead of gray out.
Version 9c 14-Jan-2018
-----------------------
jpegtran: add an option to the -wipe switch to fill the region
with the average of adjacent blocks, instead of gray out.
Thank to Caitlyn Feddock and Maddie Ziegler for inspiration.
Make range extension bits adjustable (in jpegint.h).
Thank to Robin Watts for suggestion.
Provide macros for fflush() and ferror() in jinclude.h in order
to facilitate adaption by applications using an own FILE class.
Thank to Gerhard Huber for suggestion.
Add libjpeg pkg-config file. Thank to Mark Lavi, Vincent Torri,
Patrick McMunn, and Huw Davies for suggestion.
Add sanity checks in cjpeg image reader modules.
Thank to Bingchang, Liu for reports.
Version 9b 17-Jan-2016
-----------------------
Improvements and optimizations in DCT and color calculations.
Normalize range limit array composition and access pattern.
Thank to Sia Furler and Maddie Ziegler for inspiration.
Use merged upsample with scaled DCT sizes larger than 8.
Thank to Taylor Hatala for inspiration.
Check for excessive comment lengths in argument parsing in wrjpgcom.c.
Thank to Julian Cohen for hint.
Add makefile.b32 for use with Borland C++ 32-bit (bcc32).
Thank to Joe Slater for contribution.
Document 'f' specifier for jpegtran -crop specification.
Thank to Michele Martone for suggestion.
Use defined value from header instead of hardwired number in rdswitch.c.
Thank to Robert Sprowson for hint.
Version 9a 19-Jan-2014
-----------------------
Add support for wide gamut color spaces (JFIF version 2).
Improve clarity and accuracy in color conversion modules.
Note: Requires rebuild of test images.
Extend the bit depth support to all values from 8 to 12
(BITS_IN_JSAMPLE configuration option in jmorecfg.h).
jpegtran now supports N bits sample data precision with all N from 8 to 12
in a single instance. Thank to Roland Fassauer for inspiration.
Try to resolve issues with new boolean type definition.
Thank also to v4hn for suggestion.
Enable option to use default Huffman tables for lossless compression
(for hardware solution), and in this case improve lossless RGB compression
with reversible color transform. Thank to Benny Alexandar for hint.
Extend the entropy decoding structure, so that extraneous bytes between
compressed scan data and following marker can be reported correctly.
Thank to Nigel Tao for hint.
Add jpegtran -wipe option and extension for -crop.
Thank to Andrew Senior, David Clunie, and Josef Schmid for suggestion.
Version 9 13-Jan-2013
----------------------
Add cjpeg -rgb1 option to create an RGB JPEG file, and insert
a simple reversible color transform into the processing which
significantly improves the compression.
The recommended command for lossless coding of RGB images is now
cjpeg -rgb1 -block 1 -arithmetic.
As said, this option improves the compression significantly, but
the files are not compatible with JPEG decoders prior to IJG v9
due to the included color transform.
The used color transform and marker signaling is compatible with
other JPEG standards (e.g., JPEG-LS part 2).
Remove the automatic de-ANSI-fication support (Automake 1.12).
Thank also to Nitin A Kamble for suggestion.
Add remark for jpeg_mem_dest() in jdatadst.c.
Thank to Elie-Gregoire Khoury for the hint.
Support files with invalid component identifiers (created
by Adobe PDF). Thank to Robin Watts for the suggestion.
Adapt full buffer case in jcmainct.c for use with scaled DCT.
Thank to Sergii Biloshytskyi for the suggestion.
Add type identifier for declaration of noreturn functions.
Thank to Brett L. Moore for the suggestion.
Correct argument type in format string, avoid compiler warnings.
Thank to Vincent Torri for hint.
Add missing #include directives in configuration checks, avoid
configuration errors. Thank to John Spencer for the hint.
Version 8d 15-Jan-2012
-----------------------
Add cjpeg -rgb option to create RGB JPEG files.
Using this switch suppresses the conversion from RGB
colorspace input to the default YCbCr JPEG colorspace.
This feature allows true lossless JPEG coding of RGB color images.
The recommended command for this purpose is currently
cjpeg -rgb -block 1 -arithmetic.
SmartScale capable decoder (introduced with IJG JPEG 8) required.
Thank to Michael Koch for the initial suggestion.
Add option to disable the region adjustment in the transupp crop code.
Thank to Jeffrey Friedl for the suggestion.
Thank to Richard Jones and Edd Dawson for various minor corrections.
Thank to Akim Demaille for configure.ac cleanup.
Version 8c 16-Jan-2011
-----------------------
Add option to compression library and cjpeg (-block N) to use
different DCT block size.
All N from 1 to 16 are possible. Default is 8 (baseline format).
Larger values produce higher compression,
smaller values produce higher quality.
SmartScale capable decoder (introduced with IJG JPEG 8) required.
Version 8b 16-May-2010
-----------------------
Repair problem in new memory source manager with corrupt JPEG data.
Thank to Ted Campbell and Samuel Chun for the report.
Repair problem in Makefile.am test target.
Thank to anonymous user for the report.
Support MinGW installation with automatic configure.
Thank to Volker Grabsch for the suggestion.
Version 8a 28-Feb-2010
-----------------------
Writing tables-only datastreams via jpeg_write_tables works again.
Support 32-bit BMPs (RGB image with Alpha channel) for read in cjpeg.
Thank to Brett Blackham for the suggestion.
Improve accuracy in floating point IDCT calculation.
Thank to Robert Hooke for the hint.
Version 8 10-Jan-2010
----------------------
jpegtran now supports the same -scale option as djpeg for "lossless" resize.
An implementation of the JPEG SmartScale extension is required for this
feature. A (draft) specification of the JPEG SmartScale extension is
available as a contributed document at ITU and ISO. Revision 2 or later
of the document is required (latest document version is Revision 3).
The SmartScale extension will enable more features beside lossless resize
in future implementations, as described in the document (new compression
options).
Add sanity check in BMP reader module to avoid cjpeg crash for empty input
image (thank to Isaev Ildar of ISP RAS, Moscow, RU for reporting this error).
Add data source and destination managers for read from and write to
memory buffers. New API functions jpeg_mem_src and jpeg_mem_dest.
Thank to Roberto Boni from Italy for the suggestion.
Version 7 27-Jun-2009
----------------------
New scaled DCTs implemented.
djpeg now supports scalings N/8 with all N from 1 to 16.
cjpeg now supports scalings 8/N with all N from 1 to 16.
Scaled DCTs with size larger than 8 are now also used for resolving the
common 2x2 chroma subsampling case without additional spatial resampling.
Separate spatial resampling for those kind of files is now only necessary
for N>8 scaling cases.
Furthermore, separate scaled DCT functions are provided for direct resolving
of the common asymmetric subsampling cases (2x1 and 1x2) without additional
spatial resampling.
cjpeg -quality option has been extended for support of separate quality
settings for luminance and chrominance (or in general, for every provided
quantization table slot).
New API function jpeg_default_qtables() and q_scale_factor array in library.
Added -nosmooth option to cjpeg, complementary to djpeg.
New variable "do_fancy_downsampling" in library, complement to fancy
upsampling. Fancy upsampling now uses direct DCT scaling with sizes
larger than 8. The old method is not reversible and has been removed.
Support arithmetic entropy encoding and decoding.
Added files jaricom.c, jcarith.c, jdarith.c.
Straighten the file structure:
Removed files jidctred.c, jcphuff.c, jchuff.h, jdphuff.c, jdhuff.h.
jpegtran has a new "lossless" cropping feature.
Implement -perfect option in jpegtran, new API function
jtransform_perfect_transform() in transupp. (DP 204_perfect.dpatch)
Better error messages for jpegtran fopen failure.
(DP 203_jpegtran_errmsg.dpatch)
Fix byte order issue with 16bit PPM/PGM files in rdppm.c/wrppm.c:
according to Netpbm, the de facto standard implementation of the PNM formats,
the most significant byte is first. (DP 203_rdppm.dpatch)
Add -raw option to rdjpgcom not to mangle the output.
(DP 205_rdjpgcom_raw.dpatch)
Make rdjpgcom locale aware. (DP 201_rdjpgcom_locale.dpatch)
Add extern "C" to jpeglib.h.
This avoids the need to put extern "C" { ... } around #include "jpeglib.h"
in your C++ application. Defining the symbol DONT_USE_EXTERN_C in the
configuration prevents this. (DP 202_jpeglib.h_c++.dpatch)
Version 6b 27-Mar-1998
-----------------------
jpegtran has new features for lossless image transformations (rotation
and flipping) as well as "lossless" reduction to grayscale.
jpegtran now copies comments by default; it has a -copy switch to enable
copying all APPn blocks as well, or to suppress comments. (Formerly it
always suppressed comments and APPn blocks.) jpegtran now also preserves
JFIF version and resolution information.
New decompressor library feature: COM and APPn markers found in the input
file can be saved in memory for later use by the application. (Before,
you had to code this up yourself with a custom marker processor.)
There is an unused field "void * client_data" now in compress and decompress
parameter structs; this may be useful in some applications.
JFIF version number information is now saved by the decoder and accepted by
the encoder. jpegtran uses this to copy the source file's version number,
to ensure "jpegtran -copy all" won't create bogus files that contain JFXX
extensions but claim to be version 1.01. Applications that generate their
own JFXX extension markers also (finally) have a supported way to cause the
encoder to emit JFIF version number 1.02.
djpeg's trace mode reports JFIF 1.02 thumbnail images as such, rather
than as unknown APP0 markers.
In -verbose mode, djpeg and rdjpgcom will try to print the contents of
APP12 markers as text. Some digital cameras store useful text information
in APP12 markers.
Handling of truncated data streams is more robust: blocks beyond the one in
which the error occurs will be output as uniform gray, or left unchanged
if decoding a progressive JPEG. The appearance no longer depends on the
Huffman tables being used.
Huffman tables are checked for validity much more carefully than before.
To avoid the Unisys LZW patent, djpeg's GIF output capability has been
changed to produce "uncompressed GIFs", and cjpeg's GIF input capability
has been removed altogether. We're not happy about it either, but there
seems to be no good alternative.
The configure script now supports building libjpeg as a shared library
on many flavors of Unix (all the ones that GNU libtool knows how to
build shared libraries for). Use "./configure --enable-shared" to
try this out.
New jconfig file and makefiles for Microsoft Visual C++ and Developer Studio.
Also, a jconfig file and a build script for Metrowerks CodeWarrior
on Apple Macintosh. makefile.dj has been updated for DJGPP v2, and there
are miscellaneous other minor improvements in the makefiles.
jmemmac.c now knows how to create temporary files following Mac System 7
conventions.
djpeg's -map switch is now able to read raw-format PPM files reliably.
cjpeg -progressive -restart no longer generates any unnecessary DRI markers.
Multiple calls to jpeg_simple_progression for a single JPEG object
no longer leak memory.
Version 6a 7-Feb-96
--------------------
Library initialization sequence modified to detect version mismatches
and struct field packing mismatches between library and calling application.
This change requires applications to be recompiled, but does not require
any application source code change.
All routine declarations changed to the style "GLOBAL(type) name ...",
that is, GLOBAL, LOCAL, METHODDEF, EXTERN are now macros taking the
routine's return type as an argument. This makes it possible to add
Microsoft-style linkage keywords to all the routines by changing just
these macros. Note that any application code that was using these macros
will have to be changed.
DCT coefficient quantization tables are now stored in normal array order
rather than zigzag order. Application code that calls jpeg_add_quant_table,
or otherwise manipulates quantization tables directly, will need to be
changed. If you need to make such code work with either older or newer
versions of the library, a test like "#if JPEG_LIB_VERSION >= 61" is
recommended.
djpeg's trace capability now dumps DQT tables in natural order, not zigzag
order. This allows the trace output to be made into a "-qtables" file
more easily.
New system-dependent memory manager module for use on Apple Macintosh.
Fix bug in cjpeg's -smooth option: last one or two scanlines would be
duplicates of the prior line unless the image height mod 16 was 1 or 2.
Repair minor problems in VMS, BCC, MC6 makefiles.
New configure script based on latest GNU Autoconf.
Correct the list of include files needed by MetroWerks C for ccommand().
Numerous small documentation updates.
Version 6 2-Aug-95
-------------------
Progressive JPEG support: library can read and write full progressive JPEG
files. A "buffered image" mode supports incremental decoding for on-the-fly
display of progressive images. Simply recompiling an existing IJG-v5-based
decoder with v6 should allow it to read progressive files, though of course
without any special progressive display.
New "jpegtran" application performs lossless transcoding between different
JPEG formats; primarily, it can be used to convert baseline to progressive
JPEG and vice versa. In support of jpegtran, the library now allows lossless
reading and writing of JPEG files as DCT coefficient arrays. This ability
may be of use in other applications.
Notes for programmers:
* We changed jpeg_start_decompress() to be able to suspend; this makes all
decoding modes available to suspending-input applications. However,
existing applications that use suspending input will need to be changed
to check the return value from jpeg_start_decompress(). You don't need to
do anything if you don't use a suspending data source.
* We changed the interface to the virtual array routines: access_virt_array
routines now take a count of the number of rows to access this time. The
last parameter to request_virt_array routines is now interpreted as the
maximum number of rows that may be accessed at once, but not necessarily
the height of every access.
Version 5b 15-Mar-95
---------------------
Correct bugs with grayscale images having v_samp_factor > 1.
jpeg_write_raw_data() now supports output suspension.
Correct bugs in "configure" script for case of compiling in
a directory other than the one containing the source files.
Repair bug in jquant1.c: sometimes didn't use as many colors as it could.
Borland C makefile and jconfig file work under either MS-DOS or OS/2.
Miscellaneous improvements to documentation.
Version 5a 7-Dec-94
--------------------
Changed color conversion roundoff behavior so that grayscale values are
represented exactly. (This causes test image files to change.)
Make ordered dither use 16x16 instead of 4x4 pattern for a small quality
improvement.
New configure script based on latest GNU Autoconf.
Fix configure script to handle CFLAGS correctly.
Rename *.auto files to *.cfg, so that configure script still works if
file names have been truncated for DOS.
Fix bug in rdbmp.c: didn't allow for extra data between header and image.
Modify rdppm.c/wrppm.c to handle 2-byte raw PPM/PGM formats for 12-bit data.
Fix several bugs in rdrle.c.
NEED_SHORT_EXTERNAL_NAMES option was broken.
Revise jerror.h/jerror.c for more flexibility in message table.
Repair oversight in jmemname.c NO_MKTEMP case: file could be there
but unreadable.
Version 5 24-Sep-94
--------------------
Version 5 represents a nearly complete redesign and rewrite of the IJG
software. Major user-visible changes include:
* Automatic configuration simplifies installation for most Unix systems.
* A range of speed vs. image quality tradeoffs are supported.
This includes resizing of an image during decompression: scaling down
by a factor of 1/2, 1/4, or 1/8 is handled very efficiently.
* New programs rdjpgcom and wrjpgcom allow insertion and extraction
of text comments in a JPEG file.
The application programmer's interface to the library has changed completely.
Notable improvements include:
* We have eliminated the use of callback routines for handling the
uncompressed image data. The application now sees the library as a
set of routines that it calls to read or write image data on a
scanline-by-scanline basis.
* The application image data is represented in a conventional interleaved-
pixel format, rather than as a separate array for each color channel.
This can save a copying step in many programs.
* The handling of compressed data has been cleaned up: the application can
supply routines to source or sink the compressed data. It is possible to
suspend processing on source/sink buffer overrun, although this is not
supported in all operating modes.
* All static state has been eliminated from the library, so that multiple
instances of compression or decompression can be active concurrently.
* JPEG abbreviated datastream formats are supported, ie, quantization and
Huffman tables can be stored separately from the image data.
* And not only that, but the documentation of the library has improved
considerably!
The last widely used release before the version 5 rewrite was version 4A of
18-Feb-93. Change logs before that point have been discarded, since they
are not of much interest after the rewrite.
+663
View File
@@ -0,0 +1,663 @@
# -*- coding: utf-8 -*-
# ============================================================
# gzip - 单文件 gzip 解码器(RFC 1952 + RFC 1951 deflate)
#
# OOP 范式对齐 zipfile: 数据由 GzipFile 对象持有并负责释放;
# 内部位流/Huffman 助手为模块级私有函数。
#
# 用法:
# gz: gzip.GzipFile | t.CPtr = gzip.GzipFile("train-images-idx3-ubyte.gz")
# if gz is None or gz.ok == 0: ...
# buf: bytes = gz.read_all() # 内部 malloc, read_all 返回即解压完成
# n: t.CSizeT = gz.dst_len # 原始长度(由对象持有, close 时释放)
# gz.close()
#
# 覆盖 stored/fixed/dynamic 三种 deflate 块; 不解 CRC。
# ============================================================
import t, c
import stdlib
import string
import w32.fileio as fileio
from stdint import *
# ============================================================
# GzipFile
# ============================================================
class GzipFile:
ok: t.CInt # 1 = 可正常使用
err: t.CInt # 错误码(0 无, <0 出错)
src: BYTEPTR # 整个 .gz 文件的内存副本(构造时读入, close 释放)
src_len: t.CSizeT
dst: BYTEPTR # 解压输出缓冲(read_all 生成, close 释放)
dst_len: t.CSizeT # 解压结果长度
_need_close_src: t.CInt # 是否持有 src(读失败时为 0)
def __init__(self, path: str):
self.ok = 0
self.err = 0
self.src = None
self.src_len = 0
self.dst = None
self.dst_len = 0
self._need_close_src = 0
if path is None:
self.err = -1
return
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
if f.closed:
self.err = -2 # 打不开文件
return
sz: LONGLONG = f.size()
if sz < 18:
f.close()
self.err = -3 # 文件过小
return
self.src_len = t.CSizeT(sz)
# read_all 契约: 最多读 max_count-1 字节并补 NUL; 故按 src_len+1 申请与请求
buf: BYTEPTR = stdlib.malloc(self.src_len + 1)
if buf is None:
f.close()
self.err = -4
return
got: LONG = f.read_all(buf, ULONG(self.src_len + 1))
f.close()
if got <= 0 or t.CSizeT(got) != self.src_len:
stdlib.free(buf)
self.err = -5 # 读取失败或被截断
return
# gzip 魔数校验
ub: t.CUnsignedChar | t.CPtr = buf
if ub[0] != 0x1F or ub[1] != 0x8B or ub[2] != 8:
stdlib.free(buf)
self.err = -6
return
self.src = buf
self._need_close_src = 1
self.ok = 1
def read_all(self) -> BYTEPTR:
"""解压整个成员。返回内部持有的输出缓冲(或 None); 长度见 self.dst_len。"""
if self.ok == 0 or self.src is None:
return None
if self.dst is not None:
return self.dst # 已解压过
want: t.CUInt32T = gzip_isize(self.src, self.src_len)
cap: t.CSizeT = t.CSizeT(want)
if cap == 0:
cap = self.src_len * 64 # 无 ISIZE 时保守初值
if cap < 1024:
cap = 1024
# 保险起见: ISIZE 与实际不符时报"目标缓冲不足", 就翻倍重试(正常情况下一次成功)
out: BYTEPTR = None
n: LONG = 0
while True:
out = stdlib.malloc(cap)
if out is None:
self.err = -7
return None
n = _gzip_decode(self.src, self.src_len, out, cap)
if n == -9 or n == -18 or n == -23:
stdlib.free(out)
out = None
if cap >= 1073741824:
self.err = t.CInt(n)
return None
cap = cap * 2
continue
break
if n <= 0:
stdlib.free(out)
self.err = t.CInt(n) # n 为负错误码, 原样保存
return None
self.dst = out
self.dst_len = t.CSizeT(n)
return out
def close(self):
if self._need_close_src != 0 and self.src is not None:
stdlib.free(self.src)
if self.dst is not None:
stdlib.free(self.dst)
self.src = None
self.dst = None
self.src_len = 0
self.dst_len = 0
self._need_close_src = 0
self.ok = 0
# ============================================================
# 公共访问器(跨模块读字段用函数, 避免 stub 字段访问陷阱)
# ============================================================
def gzip_open(path: str) -> GzipFile | t.CPtr:
return GzipFile(path)
def gzip_ok(gz: GzipFile | t.CPtr) -> t.CInt:
if gz is None:
return 0
return gz.ok
def gzip_err(gz: GzipFile | t.CPtr) -> t.CInt:
if gz is None:
return -1
return gz.err
def gzip_dst_len(gz: GzipFile | t.CPtr) -> t.CSizeT:
if gz is None:
return 0
return gz.dst_len
def gzip_read(gz: GzipFile | t.CPtr) -> BYTEPTR:
if gz is None:
return None
return gz.read_all()
def gzip_close(gz: GzipFile | t.CPtr):
if gz is not None:
gz.close()
def gzip_decode_bytes(src: t.CPtr, src_len: t.CSizeT,
dst: t.CPtr, dst_cap: t.CSizeT) -> LONG:
"""原始缓冲到缓冲的解压(不读文件), 供下载流程直接使用"""
return _gzip_decode(src, src_len, dst, dst_cap)
# ============================================================
# 位流状态(模块级, 单线程; 每次 _gzip_decode 重置)
# ============================================================
_g_bits: t.CUInt64T = 0
_g_bitcnt: t.CInt = 0
_g_pos: t.CSizeT = 0
_g_src: t.CPtr = None
_g_src_len: t.CSizeT = 0
def gzip_isize(src: t.CPtr, src_len: t.CSizeT) -> t.CUInt32T:
"""gzip 尾部 ISIZE(小端 u32), 即原始长度提示"""
if src is None or src_len < 4:
return 0
ub: t.CUnsignedChar | t.CPtr = src
p: t.CSizeT = src_len - 4
# 小端 u32 拼装(64 位中间量后收窄)
v64: t.CUInt64T = t.CUInt64T(ub[p]) \
+ t.CUInt64T(ub[p + 1]) * 256 \
+ t.CUInt64T(ub[p + 2]) * 65536 \
+ t.CUInt64T(ub[p + 3]) * 16777216
return t.CUInt32T(v64)
def _reset(src: t.CPtr, src_len: t.CSizeT):
global _g_bits, _g_bitcnt, _g_pos, _g_src, _g_src_len
_g_bits = 0
_g_bitcnt = 0
_g_pos = 0
_g_src = src
_g_src_len = src_len
def _fill(n: t.CInt) -> t.CInt:
global _g_bits, _g_bitcnt, _g_pos, _g_src, _g_src_len
sb: t.CUnsignedChar | t.CPtr = _g_src
while _g_bitcnt < n:
if _g_pos >= _g_src_len:
return -1
_g_bits = _g_bits | (t.CUInt64T(sb[_g_pos]) << _g_bitcnt)
_g_pos += 1
_g_bitcnt += 8
return 0
def _bits(n: t.CInt) -> t.CInt:
global _g_bits, _g_bitcnt
if _fill(n) != 0:
return -1
v: t.CUInt64T = _g_bits & ((t.CUInt64T(1) << n) - 1)
_g_bits = _g_bits >> n
_g_bitcnt -= n
return t.CInt(v)
def _byte() -> t.CInt:
return _bits(8)
def _align():
global _g_bits, _g_bitcnt
_g_bits = 0
_g_bitcnt = 0
def _decode(count: t.CInt | t.CPtr, first: t.CInt | t.CPtr,
off: t.CInt | t.CPtr, sorted: t.CInt | t.CPtr) -> t.CInt:
code: t.CInt = 0
ln: t.CInt = 1
while ln <= 15:
b: t.CInt = _bits(1)
if b < 0:
return -1
code = (code << 1) | b
if count[ln] != 0:
d: t.CInt = code - first[ln]
if d >= 0 and d < count[ln]:
return sorted[off[ln] + d]
ln += 1
return -1
def _build(first: t.CInt | t.CPtr, off: t.CInt | t.CPtr,
count: t.CInt | t.CPtr) -> t.CInt:
anylen: t.CInt = 0
l1: t.CInt = 1
while l1 <= 15:
if count[l1] != 0:
anylen = 1
l1 += 1
if anylen == 0:
return -1
left: t.CInt = 1
l2: t.CInt = 1
while l2 <= 15:
left = left << 1
if count[l2] > left:
return -1
left = left - count[l2]
l2 += 1
code: t.CInt = 0
l3: t.CInt = 1
while l3 <= 15:
code = (code + count[l3 - 1]) << 1
first[l3] = code
l3 += 1
offsum: t.CInt = 0
l4: t.CInt = 1
while l4 <= 15:
off[l4] = offsum
offsum += count[l4]
l4 += 1
return 0
def _collect(len_arr: t.CInt | t.CPtr, cnt: t.CInt | t.CPtr,
sorted_arr: t.CInt | t.CPtr, nsym: t.CInt,
pos: t.CInt | t.CPtr):
string.memset(cnt, 0, 17 * 4)
s1: t.CInt = 0
while s1 < nsym:
if len_arr[s1] != 0:
cnt[len_arr[s1]] += 1
s1 += 1
run: t.CInt = 0
l5: t.CInt = 1
while l5 <= 15:
pos[l5] = run
run += cnt[l5]
l5 += 1
s2: t.CInt = 0
while s2 < nsym:
if len_arr[s2] != 0:
sorted_arr[pos[len_arr[s2]]] = s2
pos[len_arr[s2]] += 1
s2 += 1
def _len_extra(code: t.CInt) -> t.CInt:
if code == 285:
return 0
if code >= 281:
return 5
if code >= 277:
return 4
if code >= 273:
return 3
if code >= 269:
return 2
if code >= 265:
return 1
return 0
def _len_base(code: t.CInt) -> t.CInt:
if code == 285:
return 258
if code >= 281:
return 131 + ((code - 281) << 5)
if code >= 277:
return 67 + ((code - 277) << 4)
if code >= 273:
return 35 + ((code - 273) << 3)
if code >= 269:
return 19 + ((code - 269) << 2)
if code >= 265:
return 11 + ((code - 265) << 1)
return 3 + (code - 257)
def _dist_extra(code: t.CInt) -> t.CInt:
if code < 4:
return 0
return (code - 4) // 2 + 1
def _dist_base(code: t.CInt) -> t.CInt:
if code < 4:
return code + 1
e: t.CInt = (code - 4) // 2 + 1
f: t.CInt = (1 << (e + 1)) + 1
if code % 2 == 0:
return f
return f + (1 << e)
def _cl_order_set(arr: t.CInt | t.CPtr, idx: t.CInt, val: t.CInt):
arr[idx] = val
# ============================================================
# 核心解压(模块级私有)
# ============================================================
def _gzip_decode(src: t.CPtr, src_len: t.CSizeT,
dst: t.CPtr, dst_cap: t.CSizeT) -> LONG:
if src is None or dst is None or src_len < 18 or dst_cap == 0:
return -1
ub: t.CUnsignedChar | t.CPtr = src
if ub[0] != 0x1F or ub[1] != 0x8B or ub[2] != 8:
return -2
_reset(src, src_len)
flg: t.CInt = t.CInt(ub[3])
hdr_len: t.CSizeT = 10
if flg & 0x04 != 0:
xlen: t.CSizeT = t.CSizeT(ub[hdr_len]) | (t.CSizeT(ub[hdr_len + 1]) << 8)
hdr_len += 2 + xlen
if flg & 0x08 != 0:
while hdr_len < src_len and ub[hdr_len] != 0:
hdr_len += 1
hdr_len += 1
if flg & 0x10 != 0:
while hdr_len < src_len and ub[hdr_len] != 0:
hdr_len += 1
hdr_len += 1
if flg & 0x02 != 0:
hdr_len += 2
if hdr_len >= src_len:
return -3
global _g_pos
_g_pos = hdr_len
dout: t.CUnsignedChar | t.CPtr = dst
wpos: t.CSizeT = 0
lit_len: t.CInt | t.CPtr = stdlib.malloc(288 * 4)
lit_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
lit_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
lit_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
lit_sorted: t.CInt | t.CPtr = stdlib.malloc(288 * 4)
dst_len: t.CInt | t.CPtr = stdlib.malloc(32 * 4)
dst_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
dst_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
dst_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
dst_sorted: t.CInt | t.CPtr = stdlib.malloc(32 * 4)
cl_len: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
cl_order: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
cl_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
cl_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
cl_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
cl_sorted: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
pos_tmp: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
if lit_len is None or lit_cnt is None or lit_first is None \
or lit_off is None or lit_sorted is None or dst_len is None \
or dst_cnt is None or dst_first is None or dst_off is None \
or dst_sorted is None or cl_len is None or cl_order is None \
or cl_cnt is None or cl_first is None or cl_off is None \
or cl_sorted is None or pos_tmp is None:
return -4
# 全部表缓冲清零(初始 malloc 未清零; 防个别路径读到垃圾表导致提前/错乱收尾)
string.memset(lit_len, 0, 288 * 4)
string.memset(lit_cnt, 0, 17 * 4)
string.memset(lit_first, 0, 17 * 4)
string.memset(lit_off, 0, 17 * 4)
string.memset(lit_sorted, 0, 288 * 4)
string.memset(dst_len, 0, 32 * 4)
string.memset(dst_cnt, 0, 17 * 4)
string.memset(dst_first, 0, 17 * 4)
string.memset(dst_off, 0, 17 * 4)
string.memset(dst_sorted, 0, 32 * 4)
string.memset(cl_len, 0, 19 * 4)
string.memset(cl_order, 0, 19 * 4)
string.memset(cl_cnt, 0, 17 * 4)
string.memset(cl_first, 0, 17 * 4)
string.memset(cl_off, 0, 17 * 4)
string.memset(cl_sorted, 0, 19 * 4)
string.memset(pos_tmp, 0, 17 * 4)
_cl_order_set(cl_order, 0, 16)
_cl_order_set(cl_order, 1, 17)
_cl_order_set(cl_order, 2, 18)
_cl_order_set(cl_order, 3, 0)
_cl_order_set(cl_order, 4, 8)
_cl_order_set(cl_order, 5, 7)
_cl_order_set(cl_order, 6, 9)
_cl_order_set(cl_order, 7, 6)
_cl_order_set(cl_order, 8, 10)
_cl_order_set(cl_order, 9, 5)
_cl_order_set(cl_order, 10, 11)
_cl_order_set(cl_order, 11, 4)
_cl_order_set(cl_order, 12, 12)
_cl_order_set(cl_order, 13, 3)
_cl_order_set(cl_order, 14, 13)
_cl_order_set(cl_order, 15, 2)
_cl_order_set(cl_order, 16, 14)
_cl_order_set(cl_order, 17, 1)
_cl_order_set(cl_order, 18, 15)
err: t.CInt = 0
final: t.CInt = 0
while final == 0 and err == 0:
bfinal: t.CInt = _bits(1)
btype: t.CInt = _bits(2)
if bfinal < 0 or btype < 0:
err = -5
break
final = bfinal
if btype == 0:
_align()
slen: t.CInt = _bits(16)
snlen: t.CInt = _bits(16)
if slen < 0 or snlen < 0:
err = -6
break
if (slen ^ snlen) != 0xFFFF:
err = -7
break
i: t.CSizeT = 0
while i < t.CSizeT(slen):
ch: t.CInt = _byte()
if ch < 0:
err = -8
break
if wpos >= dst_cap:
err = -9
break
dout[wpos] = t.CUnsignedChar(ch)
wpos += 1
i += 1
if err != 0:
break
elif btype == 1:
string.memset(lit_cnt, 0, 17 * 4)
j: t.CInt = 0
while j < 144:
lit_len[j] = 8
j += 1
while j < 256:
lit_len[j] = 9
j += 1
while j < 280:
lit_len[j] = 7
j += 1
while j < 288:
lit_len[j] = 8
j += 1
_collect(lit_len, lit_cnt, lit_sorted, 288, pos_tmp)
if _build(lit_first, lit_off, lit_cnt) != 0:
err = -10
break
else:
hlit: t.CInt = _bits(5) + 257
hdist: t.CInt = _bits(5) + 1
hclen: t.CInt = _bits(4) + 4
if hlit < 0 or hdist < 0 or hclen < 0:
err = -11
break
string.memset(cl_len, 0, 19 * 4)
k: t.CInt = 0
while k < hclen:
vv: t.CInt = _bits(3)
if vv < 0:
err = -12
break
cl_len[cl_order[k]] = vv
k += 1
if err != 0:
break
_collect(cl_len, cl_cnt, cl_sorted, 19, pos_tmp)
if _build(cl_first, cl_off, cl_cnt) != 0:
err = -13
break
string.memset(lit_len, 0, 288 * 4)
string.memset(dst_len, 0, 32 * 4)
sym_i: t.CInt = 0
while sym_i < hlit + hdist:
sym: t.CInt = _decode(cl_cnt, cl_first, cl_off, cl_sorted)
if sym < 0:
err = -14
break
if sym < 16:
if sym_i < hlit:
lit_len[sym_i] = sym
else:
dst_len[sym_i - hlit] = sym
sym_i += 1
elif sym == 16:
prev: t.CInt = 0
if sym_i > 0:
if sym_i - 1 < hlit:
prev = lit_len[sym_i - 1]
else:
prev = dst_len[sym_i - hlit - 1]
rp: t.CInt = _bits(2) + 3
r2: t.CInt = 0
while r2 < rp and sym_i < hlit + hdist:
if sym_i < hlit:
lit_len[sym_i] = prev
else:
dst_len[sym_i - hlit] = prev
sym_i += 1
r2 += 1
elif sym == 17:
rp2: t.CInt = _bits(3) + 3
r3: t.CInt = 0
while r3 < rp2 and sym_i < hlit + hdist:
if sym_i < hlit:
lit_len[sym_i] = 0
else:
dst_len[sym_i - hlit] = 0
sym_i += 1
r3 += 1
else:
rp3: t.CInt = _bits(7) + 11
r4: t.CInt = 0
while r4 < rp3 and sym_i < hlit + hdist:
if sym_i < hlit:
lit_len[sym_i] = 0
else:
dst_len[sym_i - hlit] = 0
sym_i += 1
r4 += 1
if err != 0:
break
_collect(lit_len, lit_cnt, lit_sorted, hlit, pos_tmp)
if _build(lit_first, lit_off, lit_cnt) != 0:
err = -15
break
_collect(dst_len, dst_cnt, dst_sorted, hdist, pos_tmp)
if _build(dst_first, dst_off, dst_cnt) != 0:
err = -16
break
if err != 0:
break
run: t.CInt = 1
while run == 1 and err == 0:
sym: t.CInt = _decode(lit_cnt, lit_first, lit_off, lit_sorted)
if sym < 0:
err = -17
break
if sym < 256:
if wpos >= dst_cap:
err = -18
break
dout[wpos] = t.CUnsignedChar(sym)
wpos += 1
elif sym == 256:
run = 0
else:
le: t.CInt = _len_extra(sym)
ev: t.CInt = _bits(le)
if ev < 0:
err = -19
break
length: t.CSizeT = t.CSizeT(_len_base(sym) + ev)
ds: t.CInt = _decode(dst_cnt, dst_first, dst_off, dst_sorted)
if ds < 0:
err = -20
break
de: t.CInt = _dist_extra(ds)
dv: t.CInt = _bits(de)
if dv < 0:
err = -21
break
dist: t.CSizeT = t.CSizeT(_dist_base(ds) + dv)
if dist == 0 or dist > wpos:
err = -22
break
ci: t.CSizeT = 0
while ci < length:
if wpos >= dst_cap:
err = -23
break
dout[wpos] = dout[wpos - dist]
wpos += 1
ci += 1
if err != 0:
break
stdlib.free(lit_len)
stdlib.free(lit_cnt)
stdlib.free(lit_first)
stdlib.free(lit_off)
stdlib.free(lit_sorted)
stdlib.free(dst_len)
stdlib.free(dst_cnt)
stdlib.free(dst_first)
stdlib.free(dst_off)
stdlib.free(dst_sorted)
stdlib.free(cl_len)
stdlib.free(cl_order)
stdlib.free(cl_cnt)
stdlib.free(cl_first)
stdlib.free(cl_off)
stdlib.free(cl_sorted)
stdlib.free(pos_tmp)
if err != 0:
return LONG(err) # err 本身为负错误码, 不能再取负(否则被上层当成"长度>0 成功")
return LONG(wpos)
+14 -1
View File
@@ -104,11 +104,24 @@ def chdir(path: str) -> int:
# =============================================================================
def mkdir(path: str) -> int:
"""创建目录。成功返回 0,失败返回 -1。"""
"""创建目录。成功返回 0,失败返回 -1。
路径按 UTF-8 解释时转 UTF-16 走 CreateDirectoryW, 否则退回 A 系列"""
if w32.win32base.utf8_is_valid(path) == 0:
result: BOOL = w32.win32file.CreateDirectoryA(path, t.CPtr(0))
if result == 0:
return -1
return 0
wp: w32.win32base.WCHARPTR = w32.win32base.utf8_to_utf16_dup(path)
if wp is None:
result_a: BOOL = w32.win32file.CreateDirectoryA(path, t.CPtr(0))
if result_a == 0:
return -1
return 0
result_w: BOOL = w32.win32file.CreateDirectoryW(t.CPtr(wp), t.CPtr(0))
stdlib.free(wp)
if result_w == 0:
return -1
return 0
def remove(path: str) -> int:
+25 -5
View File
@@ -431,8 +431,9 @@ def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str,
requests._mbuddy.free(req_buf)
# ---- 接收响应 ----
resp_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
# ---- 接收响应(缓冲自动翻倍增长, 支持 MB 级正文, 如 MNIST .gz 下载) ----
resp_cap: INT = requests.HTTP_BUFSIZ
resp_buf: str = requests._mbuddy.alloc(t.CSizeT(resp_cap))
if resp_buf is None:
_RecordErrorText("alloc response buffer failed")
openssl.SSL_free(ssl)
@@ -443,9 +444,28 @@ def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str,
return None
total: INT = 0
while total < requests.HTTP_BUFSIZ - 1:
n: INT = openssl.SSL_read(ssl, resp_buf + total, requests.HTTP_BUFSIZ - 1 - total)
if n <= 0: break
while True:
room: INT = resp_cap - 1 - total
if room <= 0:
# 缓冲已满仍可能有数据: 翻倍扩容(硬上限 256MB, 防失控流)
if resp_cap >= 268435456:
break
resp_cap = resp_cap * 2
nb: t.CVoid | t.CPtr = requests._mbuddy.realloc(resp_buf, t.CSizeT(resp_cap))
if nb is None:
_RecordErrorText("realloc response buffer failed")
requests._mbuddy.free(resp_buf)
openssl.SSL_free(ssl)
openssl.SSL_CTX_free(ctx)
sock.close()
requests._mbuddy.free(errmsg)
requests.release(resp)
return None
resp_buf = t.CChar(t.CUInt64T(nb), t.CPtr)
room = resp_cap - 1 - total
n: INT = openssl.SSL_read(ssl, resp_buf + total, room)
if n <= 0:
break
total += n
resp_buf[total] = 0
+1 -1
View File
@@ -6,4 +6,4 @@ extern def va_end(args: t.CPtr)
va_list: t.CTypedef = t.CUnsignedChar | t.CPtr
extern def arg(type: t.CType)
extern def arg(type: t.CPtr)
+925
View File
@@ -0,0 +1,925 @@
# -*- coding: utf-8 -*-
# ============================================================
# torch-lite: 仿 PyTorch 的极简张量/神经网络库(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)。
# ============================================================
from stdint import *
import t, c
import stdlib
import string
import stdio
import vipermath
import vipersimd
import memhub
# ------------------------------------------------------------
# Tensor
# ------------------------------------------------------------
@t.NoVTable
class Tensor:
Data: t.CPtr # rows*cols*8 字节连续 double
Grad: t.CPtr # 同尺寸梯度(可为 None)
Rows: sizet
Cols: sizet
# 字段: Data/Grad/Rows/Cols = 32 字节(无 vtable)
TENSOR_SIZE: t.CDefine = 32
def _zeros_block(pool: rowptr[memhub.MemBuddy], bytes_n: sizet) -> t.CPtr:
"""分配并清零一块内存"""
p: t.CPtr = pool.alloc(bytes_n)
if p is not None:
string.memset(p, 0, bytes_n)
return p
def zeros(pool: rowptr[memhub.MemBuddy], rows: sizet,
cols: sizet, with_grad: int = 1) -> Tensor | t.CPtr:
"""torch.zeros: 创建 rows x cols 的全零 Tensor, 可选分配 Grad 缓冲"""
if pool is None:
return None
t0: rowptr[Tensor] = pool.alloc(TENSOR_SIZE)
if t0 is None:
return None
string.memset(t0, 0, TENSOR_SIZE)
nb: sizet = rows * cols * sizet(8)
t0.Data = _zeros_block(pool, nb)
if t0.Data is None:
return None
if with_grad != 0:
t0.Grad = _zeros_block(pool, nb)
t0.Rows = rows
t0.Cols = cols
return t0
def from_raw(pool: rowptr[memhub.MemBuddy], data: t.CPtr,
rows: sizet, cols: sizet,
with_grad: int = 0) -> Tensor | t.CPtr:
"""torch.from_raw: 包装一块已有内存为 Tensor(不复制, 不拥有)"""
if pool is None:
return None
t0: rowptr[Tensor] = pool.alloc(TENSOR_SIZE)
if t0 is None:
return None
string.memset(t0, 0, TENSOR_SIZE)
t0.Data = data
t0.Grad = None
t0.Rows = rows
t0.Cols = cols
return t0
def tensor_free(t0: Tensor | t.CPtr):
"""释放 Tensor 外壳(数据由 MemBuddy 池统一回收, 无需手动 free)"""
pass
def tensor_data(t0: Tensor | t.CPtr) -> t.CPtr:
if t0 is None:
return None
return t0.Data
def tensor_grad(t0: Tensor | t.CPtr) -> t.CPtr:
if t0 is None:
return None
return t0.Grad
def tensor_rows(t0: Tensor | t.CPtr) -> sizet:
if t0 is None:
return 0
return t0.Rows
def tensor_cols(t0: Tensor | t.CPtr) -> sizet:
if t0 is None:
return 0
return t0.Cols
def fill(t0: Tensor | t.CPtr, val: double):
"""torch.fill: 全部元素置为 val"""
if t0 is None or t0.Data is None:
return
d: double | t.CPtr = t0.Data
n: sizet = t0.Rows * t0.Cols
i: sizet = 0
while i < n:
d[i] = val
i += 1
def zero_grad(t0: Tensor | t.CPtr):
"""torch.zero_grad: Grad 缓冲清零"""
if t0 is None or t0.Grad is None:
return
string.memset(t0.Grad, 0, t0.Rows * t0.Cols * sizet(8))
# ------------------------------------------------------------
# 确定性伪随机(LCG, minstd)与 Kaiming 均匀初始化
# ------------------------------------------------------------
_rng_state: double = 12345.0
LCG_M: double = 2147483647.0
LCG_A: double = 48271.0
def manual_seed(seed: int64):
global _rng_state
_rng_state = double(seed % int64(2147483647))
if _rng_state <= double(0.0):
_rng_state = double(12345.0)
def _next_rand() -> double:
"""(0,1) 均匀随机"""
global _rng_state
_rng_state = vipermath.fmod(_rng_state * LCG_A, LCG_M)
return _rng_state / LCG_M
def uniform_fill(t0: Tensor | t.CPtr, lo: double, hi: double):
"""U(lo, hi) 填充"""
if t0 is None or t0.Data is None:
return
d: double | t.CPtr = t0.Data
n: sizet = t0.Rows * t0.Cols
span: double = hi - lo
i: sizet = 0
while i < n:
d[i] = lo + _next_rand() * span
i += 1
def kaiming_uniform_fill(w: Tensor | t.CPtr, fan_in: sizet,
fan_out: sizet):
"""PyTorch Linear 默认初始化: U(-sqrt(6/(fan_in+fan_out)), +...)"""
bound: double = vipermath.sqrt(
double(6.0) / double(fan_in + fan_out))
uniform_fill(w, -bound, bound)
# ------------------------------------------------------------
# 基础元素级运算(结合 vipersimd 4 宽指令)
# ------------------------------------------------------------
def vector_mul_scalar4(src: t.CPtr, scalar_ptr: t.CPtr, dst: t.CPtr,
n: sizet):
"""dst = src * scalar (逐 4 元素 simd_mul_scalar4d + 标量尾数)"""
if src is None or scalar_ptr is None or dst is None:
return
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_mul_scalar4d(
t.CPtr(uint64(src) + uint64(i) * 8),
scalar_ptr,
t.CPtr(uint64(dst) + uint64(i) * 8))
i += 4
sd: double | t.CPtr = src
dd: double | t.CPtr = dst
# 尾部标量
sp: double | t.CPtr = scalar_ptr
sval: double = sp[0]
while i < n:
dd[i] = sd[i] * sval
i += 1
def vector_add_inplace(a: t.CPtr, b: t.CPtr, n: sizet):
"""a += b (逐 4 元素 simd_add_array 尾部标量)"""
if a is None or b is None:
return
tmp: t.CPtr = stdlib.malloc(8)
if tmp is None:
return
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_add_array(
t.CPtr(uint64(a) + uint64(i) * 8),
t.CPtr(uint64(b) + uint64(i) * 8),
t.CPtr(uint64(a) + uint64(i) * 8))
i += 4
da: double | t.CPtr = a
db: double | t.CPtr = b
while i < n:
da[i] = da[i] + db[i]
i += 1
stdlib.free(tmp)
# ------------------------------------------------------------
# 线性层: forward = x @ W^T + b (W: out x in, x: m x in, out: m x out)
# 内积按 k%4 用 vipersimd.simd_dot4d —— MNIST 主计算热点
# ------------------------------------------------------------
def linear_fwd(w: Tensor | t.CPtr, b: Tensor | t.CPtr,
x: Tensor | t.CPtr, out: Tensor | t.CPtr):
if w is None or b is None or x is None or out is None:
return
m: sizet = x.Rows
k: sizet = x.Cols
n: sizet = w.Rows
wd: double | t.CPtr = w.Data
bd: double | t.CPtr = b.Data
xd: double | t.CPtr = x.Data
od: double | t.CPtr = out.Data
acc_buf: t.CPtr = stdlib.malloc(8)
if acc_buf is None:
return
accp: double | t.CPtr = acc_buf
i: sizet = 0
while i < m:
j: sizet = 0
while j < n:
acc: double = bd[j]
kk: sizet = 0
while kk + 4 <= k:
vipersimd.simd_dot4d(
t.CPtr(uint64(xd) + uint64(i * k + kk) * 8),
t.CPtr(uint64(wd) + uint64(j * k + kk) * 8),
acc_buf)
acc = acc + accp[0]
kk += 4
while kk < k:
acc = acc + xd[i * k + kk] * wd[j * k + kk]
kk += 1
od[i * n + j] = acc
j += 1
i += 1
stdlib.free(acc_buf)
# ------------------------------------------------------------
# 线性层反向: 输入 w(out,in) x(m,in) g(m,out)
# dw[j,i] += sum_b g[b,j]*x[b,i] ; db[j] += sum_b g[b,j]
# dx[b,i] = sum_j g[b,j]*w[j,i]
# 注意 dw/db 按"累加"语义(调用前清零 Grad)
# ------------------------------------------------------------
def linear_bwd(w: Tensor | t.CPtr, x: Tensor | t.CPtr,
g: Tensor | t.CPtr, dw: Tensor | t.CPtr,
db: Tensor | t.CPtr, dx: Tensor | t.CPtr,
accum: int = 0):
if w is None or x is None or g is None:
return
if dw is None or db is None or dx is None:
return
m: sizet = x.Rows
k: sizet = x.Cols
n: sizet = w.Rows
wd: double | t.CPtr = w.Data
xd: double | t.CPtr = x.Data
gd: double | t.CPtr = g.Data
dwd: double | t.CPtr = dw.Data
dbd: double | t.CPtr = db.Data
dxd: double | t.CPtr = dx.Data
# dw/db/dx 清零(dx 恒清零: 它是本样本的输入梯度, 不跨样本累积)
# accum=1 时保留 dw/db 现值 —— 逐样本反向累积整批参数梯度的调用方用
if accum == 0:
string.memset(dw.Data, 0, n * k * sizet(8))
string.memset(db.Data, 0, n * sizet(8))
string.memset(dx.Data, 0, m * k * sizet(8))
b: sizet = 0
while b < m:
j: sizet = 0
while j < n:
gj: double = gd[b * n + j]
if gj != double(0.0):
dbd[j] = dbd[j] + gj
i: sizet = 0
while i < k:
dwd[j * k + i] = dwd[j * k + i] + gj * xd[b * k + i]
dxd[b * k + i] = dxd[b * k + i] + gj * wd[j * k + i]
i += 1
j += 1
b += 1
# ------------------------------------------------------------
# ReLU (simd_max4d 版本) 与反向
# ------------------------------------------------------------
def relu_fwd(x: Tensor | t.CPtr, out: Tensor | t.CPtr):
if x is None or out is None:
return
n: sizet = x.Rows * x.Cols
zero4: t.CPtr = stdlib.malloc(32)
if zero4 is None:
return
string.memset(zero4, 0, 32)
xd: double | t.CPtr = x.Data
od: double | t.CPtr = out.Data
i: sizet = 0
while i + 4 <= n:
vipersimd.simd_max4d(
t.CPtr(uint64(xd) + uint64(i) * 8),
zero4,
t.CPtr(uint64(od) + uint64(i) * 8))
i += 4
while i < n:
if xd[i] > double(0.0):
od[i] = xd[i]
else:
od[i] = double(0.0)
i += 1
stdlib.free(zero4)
def relu_backward(x: Tensor | t.CPtr, g: Tensor | t.CPtr,
gx: Tensor | t.CPtr):
"""gx = g * (x > 0)"""
if x is None or g is None or gx is None:
return
n: sizet = x.Rows * x.Cols
xd: double | t.CPtr = x.Data
gd: double | t.CPtr = g.Data
od: double | t.CPtr = gx.Data
i: sizet = 0
while i < n:
if xd[i] > double(0.0):
od[i] = gd[i]
else:
od[i] = double(0.0)
i += 1
# ------------------------------------------------------------
# softmax + CrossEntropy(融合, 数值稳定) —— MNIST 输出层
# logits: (m, C), labels: m 个 int (0..C-1)
# loss 写入 *loss, 梯度(已按 1/m 归一)写入 grad (m, C)
# ------------------------------------------------------------
def softmax_crossentropy(logits: Tensor | t.CPtr,
labels: int | t.CPtr,
n_labels: sizet,
loss: double | t.CPtr,
grad: Tensor | t.CPtr):
if logits is None or labels is None or grad is None:
return
if loss is None:
return
m: sizet = logits.Rows
c: sizet = logits.Cols
ld: double | t.CPtr = logits.Data
gd: double | t.CPtr = grad.Data
lb: int | t.CPtr = labels
total: double = double(0.0)
inv_m: double = double(1.0) / double(m)
b: sizet = 0
while b < m:
# 数值稳定 softmax
mx: double = ld[b * c]
j: sizet = 1
while j < c:
if ld[b * c + j] > mx:
mx = ld[b * c + j]
j += 1
s: double = double(0.0)
j = 0
while j < c:
s = s + vipermath.exp(ld[b * c + j] - mx)
j += 1
y: int = lb[b]
# loss += -(logits[lab] - max - log(s))
total = total - (ld[b * c + sizet(y)] - mx - vipermath.log(s))
# 梯度: (softmax - onehot) / m
j = 0
while j < c:
p: double = vipermath.exp(ld[b * c + j] - mx) / s
o: double = double(0.0)
if sizet(y) == j:
o = double(1.0)
gd[b * c + j] = (p - o) * inv_m
j += 1
b += 1
loss[0] = total
# ------------------------------------------------------------
# 每行 argmax (分类预测)
# ------------------------------------------------------------
def row_argmax(x: Tensor | t.CPtr, pred: int | t.CPtr):
if x is None or pred is None:
return
m: sizet = x.Rows
c: sizet = x.Cols
d: double | t.CPtr = x.Data
i: sizet = 0
while i < m:
best: sizet = 0
bv: double = d[i * c]
j: sizet = 1
while j < c:
if d[i * c + j] > bv:
bv = d[i * c + j]
best = j
j += 1
pred[i] = int(best)
i += 1
# ------------------------------------------------------------
# SGD: data -= lr * grad(全量梯度) / 重球法: v = m*v+g, data -= lr*v
# ------------------------------------------------------------
def sgd_step(param: Tensor | t.CPtr, lr: double):
if param is None or param.Data is None or param.Grad is None:
return
d: double | t.CPtr = param.Data
g: double | t.CPtr = param.Grad
n: sizet = param.Rows * param.Cols
i: sizet = 0
while i < n:
d[i] = d[i] - lr * g[i]
i += 1
def sgd_step_momentum(param: Tensor | t.CPtr, vel: t.CPtr,
lr: double, momentum: double):
"""带动量(重球法)的 SGD: v = m*v + g ; w -= lr*v
vel 是与 param 同元素个数的 double 缓冲, 由调用方分配并清零(跨 step 持有);
vel 传 None 时退化为普通 sgd_step。动量是 CNN/MLP 训练收敛速度的关键 ——
纯 SGD 在固定 lr 下前几个 epoch 很慢且容易卡在低准确率。"""
if param is None or param.Data is None or param.Grad is None:
return
if vel is None:
sgd_step(param, lr)
return
d: double | t.CPtr = param.Data
g: double | t.CPtr = param.Grad
v: double | t.CPtr = vel
n: sizet = param.Rows * param.Cols
i: sizet = 0
while i < n:
vv: double = momentum * v[i] + g[i]
v[i] = vv
d[i] = d[i] - lr * vv
i += 1
# ------------------------------------------------------------
# 逐元素乘加 (调试/扩展用, vipersimd fma_array)
# a = a*b + c
# ------------------------------------------------------------
def fma_array_inplace(a: Tensor | t.CPtr, b: t.CPtr, c: t.CPtr):
if a is None or a.Data is None:
return
vipersimd.simd_fma_array(a.Data, b, c, a.Data, a.Rows * a.Cols)
# ------------------------------------------------------------
# Conv2d / MaxPool2d —— 轻量 CNN 用的原始缓冲算子
#
# 这些算子直接吃裸缓冲(不用 Tensor: torch.Tensor 只有 2D 形状),
# 布局一律 NCHW 连续 double; 卷积权重 [cout][cin][k][k]。
# 约定:
# *_fwd 覆盖写输出
# conv2d_bwd_dx 先清零再累加(逐样本输入梯度, 不跨样本累积)
# maxpool2d_bwd 同上
# conv2d_bwd_dw += 累加参数梯度(跨样本累积, 批次开始由调用方清零)
#
# SIMD: 三个卷积算子的内层都按"输出列"做 4 路 AVX2 double。
# 依据是 stride==1 时相邻输出列恰好对应相邻输入列, 故一列 4 个 double
# 在内存里连续, 可直接 vmovupd + vfmadd213pd, 权重标量 vbroadcastsd。
# 代价是需要一个"安全列区间": 4 条 lane 的输出列都在 [0,wout) 内, 且它们
# 触及的输入列 [ox-pad, ox+3+k-1-pad] 全在 [0,win) 内 —— 即
# ox ∈ [pad, win+pad-k-3]。区间外的列与 stride!=1 一律走原标量分支。
# (maxpool2d 不做 SIMD: k==stride==2 时 4 个窗口起点间隔 2 个 double,
# 不是连续地址, 无 gather 原语; 且它占总计算量不到 1%。)
# ------------------------------------------------------------
def _dptr(base: t.CPtr, elem: sizet) -> t.CPtr:
"""裸 double 缓冲的元素下标 → 字节地址
裸 t.CPtr 是 i8*, `p + n` 走的是字节偏移; 而 vipersimd 的 4 路原语按
"4 个 double = 32 字节"读写, 所以下标必须显式 ×8。
(与 linear_fwd 里的 t.CPtr(uint64(p) + i * 8) 同一写法。)"""
return t.CPtr(uint64(base) + uint64(elem) * 8)
def conv2d_fwd(x: t.CPtr, w: t.CPtr, b: t.CPtr, out: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""out[n][cout][hout][wout] = conv2d(x, w) + b
hout = (hin + 2*pad - k)/stride + 1(wout 同理, 整除向下取整)"""
if x is None or w is None or out is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
xd: double | t.CPtr = x
wd: double | t.CPtr = w
od: double | t.CPtr = out
kk: sizet = cin * k * k
# SIMD 安全区间(见文件头说明)
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
wb: t.CPtr = None # 权重标量 × 4 路广播副本(kk 个标量)
t4: t.CPtr = None # 4 路累加器
if simd_on != 0:
wb = stdlib.malloc(kk * 32)
t4 = stdlib.malloc(32)
if wb is None or t4 is None:
if wb is not None:
stdlib.free(wb)
if t4 is not None:
stdlib.free(t4)
wb = None
t4 = None
simd_on = 0
wbd: double | t.CPtr = wb
t4d: double | t.CPtr = t4
bi: sizet = 0
while bi < n:
co: sizet = 0
while co < cout:
bias: double = double(0.0)
if b is not None:
bd: double | t.CPtr = b
bias = bd[co]
# 权重标量预广播: 每个 (ci,ky,kx) 一个 4 路副本, 本通道所有输出
# 行/列共用。否则 vbroadcastsd(4 次 store) 会跟着 4 路循环重复,
# 把 SIMD 的收益吃掉一半。
if simd_on != 0:
wb4: sizet = 0
while wb4 < kk:
wq: double = wd[co * kk + wb4]
wbd[wb4 * 4] = wq
wbd[wb4 * 4 + 1] = wq
wbd[wb4 * 4 + 2] = wq
wbd[wb4 * 4 + 3] = wq
wb4 += 1
oy: sizet = 0
while oy < hout:
oy0: sizet = oy * stride
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
# ---- 4 路: 一次算 4 个相邻输出列 ----
t4d[0] = bias
t4d[1] = bias
t4d[2] = bias
t4d[3] = bias
ci: sizet = 0
while ci < cin:
ky: sizet = 0
while ky < k:
iy: sizet = oy0 + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
kx: sizet = 0
while kx < k:
vipersimd.simd_fma4d(
_dptr(x, xrow + ox + kx - pad),
_dptr(wb, ((ci * k + ky) * k + kx) * 4),
t4, t4)
kx += 1
ky += 1
ci += 1
ob: sizet = ((bi * cout + co) * hout + oy) * wout + ox
od[ob] = t4d[0]
od[ob + 1] = t4d[1]
od[ob + 2] = t4d[2]
od[ob + 3] = t4d[3]
ox += 4
else:
# ---- 标量: 边界列 / stride != 1 ----
ox0: sizet = ox * stride
acc: double = bias
ci2: sizet = 0
while ci2 < cin:
ky2: sizet = 0
while ky2 < k:
iy2: sizet = oy0 + ky2
if iy2 >= pad:
yy2: sizet = iy2 - pad
if yy2 < hin:
kx2: sizet = 0
while kx2 < k:
ix2: sizet = ox0 + kx2
if ix2 >= pad:
xx2: sizet = ix2 - pad
if xx2 < win:
acc = acc + \
wd[co * kk + (ci2 * k + ky2) * k + kx2] * \
xd[((bi * cin + ci2) * hin + yy2) * win + xx2]
kx2 += 1
ky2 += 1
ci2 += 1
od[((bi * cout + co) * hout + oy) * wout + ox] = acc
ox += 1
oy += 1
co += 1
bi += 1
if wb is not None:
stdlib.free(wb)
if t4 is not None:
stdlib.free(t4)
def conv2d_bwd_dw(x: t.CPtr, dout: t.CPtr, dw: t.CPtr, db: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""累加: dw[co][ci][ky][kx] += sum dout*x ; db[co] += sum dout
(+= 语义, 调用方在批次开始前清零)
循环顺序为 (ci,ky,kx) 在外、(oy,ox) 在内: 每个权重元素只做一次
读-改-写; 内层按输出列 4 路 SIMD 累加, 最后 hsum 成标量。"""
if x is None or dout is None or dw is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
xd: double | t.CPtr = x
gd: double | t.CPtr = dout
dwd: double | t.CPtr = dw
dbd: double | t.CPtr = None
if db is not None:
dbd = db
kk: sizet = cin * k * k
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
acc: t.CPtr = None
if simd_on != 0:
acc = stdlib.malloc(32)
if acc is None:
simd_on = 0
accd: double | t.CPtr = acc
bi: sizet = 0
while bi < n:
co: sizet = 0
while co < cout:
gp: sizet = (bi * cout + co) * hout * wout
# ---- db[co] += sum(g): g 的整个通道平面是连续的 ----
if dbd is not None:
tot0: double = double(0.0)
if simd_on != 0:
accd[0] = double(0.0)
accd[1] = double(0.0)
accd[2] = double(0.0)
accd[3] = double(0.0)
gcnt: sizet = hout * wout
gof: sizet = 0
while gof + 4 <= gcnt:
if simd_on != 0:
vipersimd.simd_add4d(_dptr(dout, gp + gof), acc, acc)
else:
tot0 = tot0 + gd[gp + gof]
tot0 = tot0 + gd[gp + gof + 1]
tot0 = tot0 + gd[gp + gof + 2]
tot0 = tot0 + gd[gp + gof + 3]
gof += 4
while gof < gcnt:
tot0 = tot0 + gd[gp + gof]
gof += 1
if simd_on != 0:
vipersimd.simd_hsum4d(acc, acc)
tot0 = tot0 + accd[0]
dbd[co] = dbd[co] + tot0
# ---- dw[co][ci][ky][kx] ----
ci: sizet = 0
while ci < cin:
ky: sizet = 0
while ky < k:
kx: sizet = 0
while kx < k:
tot: double = double(0.0)
if simd_on != 0:
accd[0] = double(0.0)
accd[1] = double(0.0)
accd[2] = double(0.0)
accd[3] = double(0.0)
oy: sizet = 0
while oy < hout:
iy: sizet = oy * stride + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
goff: sizet = gp + oy * wout
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
vipersimd.simd_fma4d(
_dptr(dout, goff + ox),
_dptr(x, xrow + ox + kx - pad),
acc, acc)
ox += 4
else:
ix: sizet = ox * stride + kx
if ix >= pad:
xx: sizet = ix - pad
if xx < win:
tot = tot + gd[goff + ox] * \
xd[xrow + xx]
ox += 1
oy += 1
if simd_on != 0:
vipersimd.simd_hsum4d(acc, acc)
tot = tot + accd[0]
wj: sizet = co * kk + (ci * k + ky) * k + kx
dwd[wj] = dwd[wj] + tot
kx += 1
ky += 1
ci += 1
co += 1
bi += 1
if acc is not None:
stdlib.free(acc)
def conv2d_bwd_dx(w: t.CPtr, dout: t.CPtr, dx: t.CPtr,
n: sizet, cin: sizet, hin: sizet, win: sizet,
cout: sizet, k: sizet, stride: sizet,
pad: sizet):
"""dx 先清零再累加(逐样本输入梯度): dx[ci][iy][ix] += sum dout*w
stride==1 时内层按输出列 4 路 SIMD: 4 条 lane 对应的 dx 单元也是连续的,
故可直接对 dx 做 in-place fma(g4 * w_broadcast + dx4)。"""
if w is None or dout is None or dx is None:
return
hout: sizet = (hin + pad * 2 - k) / stride + 1
wout: sizet = (win + pad * 2 - k) / stride + 1
wd: double | t.CPtr = w
gd: double | t.CPtr = dout
dxd: double | t.CPtr = dx
string.memset(dx, 0, n * cin * hin * win * sizet(8))
kk: sizet = cin * k * k
simd_on: int = 0
smax: sizet = 0
if stride == 1 and win + pad >= k + 3:
simd_on = 1
smax = win + pad - (k + 3)
wb: t.CPtr = None # 整个权重张量的 4 路广播副本(cout*kk 个标量)
if simd_on != 0:
wb = stdlib.malloc(cout * kk * 32)
if wb is None:
simd_on = 0
wbd: double | t.CPtr = wb
if simd_on != 0:
# 权重在一次调用内不变 → 整体预广播一次(注意别在 4 路循环里重复
# vbroadcastsd: 那是 4 次 store, 会把 SIMD 的收益吃掉一半)
wjn: sizet = cout * kk
wj4: sizet = 0
while wj4 < wjn:
wq2: double = wd[wj4]
wbd[wj4 * 4] = wq2
wbd[wj4 * 4 + 1] = wq2
wbd[wj4 * 4 + 2] = wq2
wbd[wj4 * 4 + 3] = wq2
wj4 += 1
bi: sizet = 0
while bi < n:
ci: sizet = 0
while ci < cin:
ky: sizet = 0
while ky < k:
oy: sizet = 0
while oy < hout:
iy: sizet = oy * stride + ky
if iy >= pad:
yy: sizet = iy - pad
if yy < hin:
dxrow: sizet = ((bi * cin + ci) * hin + yy) * win
ox: sizet = 0
while ox < wout:
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
co: sizet = 0
while co < cout:
kx: sizet = 0
while kx < k:
goff: sizet = ((bi * cout + co) * hout + oy) * wout + ox
doff: sizet = dxrow + ox + kx - pad
vipersimd.simd_fma4d(
_dptr(dout, goff),
_dptr(wb, (co * kk + (ci * k + ky) * k + kx) * 4),
_dptr(dx, doff), _dptr(dx, doff))
kx += 1
co += 1
ox += 4
else:
co2: sizet = 0
while co2 < cout:
kx2: sizet = 0
while kx2 < k:
ix: sizet = ox * stride + kx2
if ix >= pad:
xx: sizet = ix - pad
if xx < win:
xi: sizet = dxrow + xx
gi: sizet = ((bi * cout + co2) * hout + oy) * wout + ox
dxd[xi] = dxd[xi] + \
gd[gi] * wd[co2 * kk + (ci * k + ky) * k + kx2]
kx2 += 1
co2 += 1
ox += 1
oy += 1
ky += 1
ci += 1
bi += 1
if wb is not None:
stdlib.free(wb)
# ------------------------------------------------------------
# MaxPool2d (k x k, stride 通常 = k)
# ------------------------------------------------------------
def maxpool2d_fwd(x: t.CPtr, out: t.CPtr, argmax: t.CPtr,
n: sizet, c: sizet, hin: sizet, win: sizet,
k: sizet, stride: sizet):
"""argmax: int 数组 [n*c*hout*wout], 记窗口内最大值在本通道内的下标(yy*win+xx)"""
if x is None or out is None or argmax is None:
return
hout: sizet = (hin - k) / stride + 1
wout: sizet = (win - k) / stride + 1
xd: double | t.CPtr = x
od: double | t.CPtr = out
am: int | t.CPtr = argmax
bi: sizet = 0
while bi < n:
ch: sizet = 0
while ch < c:
plano: sizet = (bi * c + ch) * hin * win
oy: sizet = 0
while oy < hout:
oy0: sizet = oy * stride
ox: sizet = 0
while ox < wout:
ox0: sizet = ox * stride
best: double = double(0.0)
bidx: int = 0
first: int = 1
ky: sizet = 0
while ky < k:
yy: sizet = oy0 + ky
kx: sizet = 0
while kx < k:
xx: sizet = ox0 + kx
v: double = xd[plano + yy * win + xx]
if first != 0 or v > best:
best = v
bidx = int(yy * win + xx)
first = 0
kx += 1
ky += 1
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
od[oi] = best
am[oi] = bidx
ox += 1
oy += 1
ch += 1
bi += 1
def maxpool2d_bwd(dout: t.CPtr, argmax: t.CPtr, dx: t.CPtr,
n: sizet, c: sizet, hin: sizet, win: sizet,
k: sizet, stride: sizet):
"""dx 先清零再散射累加: dx[argmax] += dout"""
if dout is None or argmax is None or dx is None:
return
hout: sizet = (hin - k) / stride + 1
wout: sizet = (win - k) / stride + 1
gd: double | t.CPtr = dout
am: int | t.CPtr = argmax
dxd: double | t.CPtr = dx
string.memset(dx, 0, n * c * hin * win * sizet(8))
bi: sizet = 0
while bi < n:
ch: sizet = 0
while ch < c:
plano: sizet = (bi * c + ch) * hin * win
oy: sizet = 0
while oy < hout:
ox: sizet = 0
while ox < wout:
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
tgt: sizet = plano + sizet(am[oi])
dxd[tgt] = dxd[tgt] + gd[oi]
ox += 1
oy += 1
ch += 1
bi += 1
+36 -36
View File
@@ -168,48 +168,48 @@ def tanh(x: t.CDouble) -> t.CDouble:
def exp(x: t.CDouble) -> t.CDouble:
"""e^x
范围归约 + 短泰勒: 直接对 x 展开 18 项泰勒在 |x| 稍大时就会发散
(x=-10 得 54.5, 真值 4.5e-5; x=-30 得 3.7e10), 负数端更是给出巨大正值,
会让 softmax 的分母被撑爆 → 所有概率 ≈ 0。改为先做
x = k*ln2 + r (|r| <= ln2/2), 再算 exp(r) 后乘 2^k。
"""
# 先用"有限且落在范围里"的正向判断过一遍: NaN 与任何数比较都为假, 会同时漏过
# 下面两个判断, 而 t.CInt(NaN) 在 x86 上是 INT_MIN -> 后面 2^k 缩放循环要跑
# 21 亿次, 表现为"卡死"。故 NaN/越界必须在这里挡掉。
if x <= t.CDouble(709.0) and x >= t.CDouble(-745.0):
pass
else:
if x > t.CDouble(709.0):
return t.CDouble(1.7976931348623157e308)
if x < t.CDouble(-709.0):
if x < t.CDouble(-745.0):
return t.CDouble(0.0)
return x # NaN: 原样返回, 让 NaN 传播而不是卡死
# 归约: 把 x 折到 [-ln2/2, ln2/2], 整数部分累积到 k (内联取整, 避免前向引用)
ln2: t.CDouble = U_M_LN2
q: t.CDouble = x / ln2
k: t.CInt = t.CInt(q)
frac: t.CDouble = q - t.CDouble(k)
if frac > t.CDouble(0.5):
k = k + 1
elif frac < t.CDouble(-0.5):
k = k - 1
r: t.CDouble = x - t.CDouble(k) * ln2
# exp(r), |r| <= 0.3466: 12 项后截断误差 < 1e-17
result: t.CDouble = t.CDouble(1.0)
term: t.CDouble = t.CDouble(1.0)
term = term * x / t.CDouble(1.0)
result = result + term
term = term * x / t.CDouble(2.0)
result = result + term
term = term * x / t.CDouble(3.0)
result = result + term
term = term * x / t.CDouble(4.0)
result = result + term
term = term * x / t.CDouble(5.0)
result = result + term
term = term * x / t.CDouble(6.0)
result = result + term
term = term * x / t.CDouble(7.0)
result = result + term
term = term * x / t.CDouble(8.0)
result = result + term
term = term * x / t.CDouble(9.0)
result = result + term
term = term * x / t.CDouble(10.0)
result = result + term
term = term * x / t.CDouble(11.0)
result = result + term
term = term * x / t.CDouble(12.0)
result = result + term
term = term * x / t.CDouble(13.0)
result = result + term
term = term * x / t.CDouble(14.0)
result = result + term
term = term * x / t.CDouble(15.0)
result = result + term
term = term * x / t.CDouble(16.0)
result = result + term
term = term * x / t.CDouble(17.0)
result = result + term
term = term * x / t.CDouble(18.0)
n: t.CInt = 1
while n <= 12:
term = term * r / t.CDouble(n)
result = result + term
n += 1
while k > 0:
result = result * t.CDouble(2.0)
k = k - 1
while k < 0:
result = result * t.CDouble(0.5)
k = k + 1
return result
+103
View File
@@ -428,6 +428,109 @@ vzeroupper""",
'ymm12', 'ymm13', 'ymm14', 'ymm15', 'rcx', 'rdx', 'r8', 'r9', 'r10', 'r11'])
def scalar_fma_array(a: t.CPtr, b: t.CPtr, c_val: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
"""标量(逐元素)融合乘加: out[i] = a[i]*b[i] + c_val[i], 一次只算 1 个 double。
与 simd_fma_array 是同一数学内核的"未向量化"对照实现:
* 指令用标量 FMA `vfmadd213sd`(xmm 低位), 与 SIMD 版的 `vfmadd213pd`
同为硬件融合乘加、同为单次舍入 -> 逐元素结果与 SIMD 版**逐位一致**,
CHECK 可直接对照;
* 每次循环 3 读 1 写、8 字节粒度, 相比 ×4 的 ymm 少 4 倍数据并行度,
用来量化"向量化本身"带来的收益(SIMD 加速比)。
n 须 >= 1; n == 0 直接跳过。"""
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
mov rdx, {c.AsmInp(c_val, t.ASM_DESCR.REG_ANY)}
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
test rcx, rcx
jz .Lsca_end
.Lsca_loop:
vmovsd xmm0, [r8]
vmovsd xmm1, [r9]
vfmadd213sd xmm0, xmm1, [rdx]
vmovsd [r10], xmm0
add r8, 8
add r9, 8
add rdx, 8
add r10, 8
dec rcx
jnz .Lsca_loop
.Lsca_end:""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'xmm1',
'rcx', 'rdx', 'r8', 'r9', 'r10'])
def scalar_add_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
"""标量(逐元素)加法: out[i] = a[i] + b[i], 一次 1 个 double。
`simd_add_array` 的未向量化对照: 标量 `vaddsd` vs SIMD `vaddpd`,
同为 IEEE 加法 -> 结果逐位一致。"""
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
test rcx, rcx
jz .Lsca_add_end
.Lsca_add_loop:
vmovsd xmm0, [r8]
vaddsd xmm0, xmm0, [r9]
vmovsd [r10], xmm0
add r8, 8
add r9, 8
add r10, 8
dec rcx
jnz .Lsca_add_loop
.Lsca_add_end:""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r9', 'r10'])
def scalar_mul_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
"""标量(逐元素)乘法: out[i] = a[i] * b[i], 一次 1 个 double。
`simd_mul_array` 的未向量化对照: 标量 `vmulsd` vs SIMD `vmulpd`,
同为 IEEE 乘法 -> 结果逐位一致。"""
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
test rcx, rcx
jz .Lsca_mul_end
.Lsca_mul_loop:
vmovsd xmm0, [r8]
vmulsd xmm0, xmm0, [r9]
vmovsd [r10], xmm0
add r8, 8
add r9, 8
add r10, 8
dec rcx
jnz .Lsca_mul_loop
.Lsca_mul_end:""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r9', 'r10'])
def scalar_sqrt_array(a: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
"""标量(逐元素)平方根: out[i] = sqrt(a[i]), 一次 1 个 double。
`simd_sqrt_array` 的未向量化对照: 标量 `vsqrtsd` vs SIMD `vsqrtpd`,
两者都是 IEEE 正确舍入的平方根 -> 结果逐位一致。"""
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
test rcx, rcx
jz .Lsca_sqrt_end
.Lsca_sqrt_loop:
vmovsd xmm0, [r8]
vsqrtsd xmm0, xmm0, xmm0
vmovsd [r10], xmm0
add r8, 8
add r10, 8
dec rcx
jnz .Lsca_sqrt_loop
.Lsca_sqrt_end:""",
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r10'])
def simd_div_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
"""AVX2 batch: out[i:i+4] = a[i:i+4] / b[i:i+4]"""
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
+4
View File
@@ -27,6 +27,7 @@ find_package(Qt6 6.5 REQUIRED COMPONENTS
Gui
Widgets
Network
Charts
)
# Win32 GUI 应用不需要控制台
@@ -40,6 +41,7 @@ set(VQT6_SOURCES
_bridge_widgets.cpp
_bridge_gui.cpp
_bridge_network.cpp
_bridge_canvas.cpp
)
set(VQT6_HEADERS
@@ -47,6 +49,7 @@ set(VQT6_HEADERS
_bridge_widgets.h
_bridge_gui.h
_bridge_network.h
_bridge_canvas.h
)
# Qt 模块
@@ -55,6 +58,7 @@ set(VQT6_QT_MODULES
Qt6::Gui
Qt6::Widgets
Qt6::Network
Qt6::Charts
)
# ============================================================
+517 -169
View File
@@ -23,6 +23,15 @@ from vqt6._qtnetwork import *
# ============================================================
# 高级 API 类(Pythonic 包装,不透明指针)
#
# 为什么每个类都写 __new__:
# Viper 的类实例默认是"创建函数当前帧的 alloca"(见
# CallGenCtor._translate_struct_ctor: tmp = build_alloca(struct_ty))。
# 一旦包装对象被存进字段、或被 return 出函数,帧销毁后该指针即悬垂——
# 表现为读 _ptr 得到垃圾、再把垃圾传给 Qt(如 QLayout::addWidget 崩溃)。
# 编译器在类定义了 __new__ 时改用其返回值作存储指针,故这里统一
# stdlib.malloc 堆分配包装壳(单字段 { _ptr } = 8 字节,进程期存活)。
# 同模块内即用即弃的场景不受影响;跨帧/跨模块存取则必须靠它。
# ============================================================
# ============================================================
@@ -140,6 +149,9 @@ class QApplication:
"""QApplication 包装(不透明指针)"""
_ptr: QtAppPtr
def __new__(self, argv: list[str] | t.CPtr) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, argv: list[str] | t.CPtr):
# 把 argv 转换为 argc/argv 数组
argc: t.CInt = 1
@@ -168,12 +180,27 @@ class QApplication:
def desktop_height(self) -> int:
return vqt6_qapplication_desktop_height(self._ptr)
def set_font_family(self, family: str):
"""设置应用级字体族(全局生效)
必须在创建任何控件之前调用:Qt 是在控件构造时从应用字体继承的,
之后再设只会影响"此后新建"的控件。
这里必须走应用字体而不是给主窗口挂样式表(font-family):Qt Charts 的
标题/图例/坐标轴标签画在 QChart(QGraphicsWidget)上,样式表够不到它们,
只有应用字体能一并覆盖。
"""
vqt6_qapplication_set_font_family_utf8(self._ptr, family)
@t.NoVTable
class QWidget:
"""QWidget 包装"""
_ptr: QtWidgetPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qwidget_create_with_parent(parent._ptr)
@@ -236,6 +263,9 @@ class QLabel:
"""QLabel 包装"""
_ptr: QtLabelPtr
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, text: str = "", parent: "QWidget | None" = None):
text_qs: QtStringPtr = vqt6_qstring_create(text)
if parent is not None and parent._ptr is not None:
@@ -273,6 +303,9 @@ class QPushButton:
"""QPushButton 包装"""
_ptr: QtPushButtonPtr
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, text: str = "", parent: "QWidget | None" = None):
text_qs: QtStringPtr = vqt6_qstring_create(text)
if parent is not None and parent._ptr is not None:
@@ -328,13 +361,183 @@ class QPushButton:
# ============================================================
# 窗口:QMainWindow / QDialog
# 菜单:QAction / QMenu / QMenuBar / QStatusBar
#
# 按依赖顺序定义(QAction → QMenu → QMenuBar → QStatusBar), 并整体排在
# QMainWindow 之前, 使 addMenu()/addActionWithText() 的返回注解不再前向引用。
#
# 注意: 返回"包装类对象"(按值 struct)的方法无法跨模块解析返回类型——
# 依赖模块 text.ll 里是 %"sha1.QStatusBar" 这类 struct 值(不含 *,
# _ParseLLVMRetTyStr 解析失败), 而 vpi 缓存的是带引号的前向注解
# ('QStatusBar'), 同样解析失败 → 调用方 fatal(mro_ret)。
# 故 QMainWindow.menuBar/statusBar 一律返回裸指针。
# ============================================================
@t.NoVTable
class QAction:
"""QAction 包装"""
_ptr: QtActionPtr
def __init__(self, text: str = ""):
if text != "":
qs: QtStringPtr = vqt6_qstring_create(text)
self._ptr = vqt6_qaction_create_with_text(qs)
vqt6_qstring_free(qs)
else:
self._ptr = vqt6_qaction_create()
if self._ptr is None:
raise RuntimeError("Failed to create QAction")
def setText(self, text: str):
qs: QtStringPtr = vqt6_qstring_create(text)
vqt6_qaction_set_text(self._ptr, qs)
vqt6_qstring_free(qs)
def text(self) -> str:
return _w_text_of(vqt6_qaction_text(self._ptr))
def setShortcut(self, key_seq: str):
qs: QtStringPtr = vqt6_qstring_create(key_seq)
vqt6_qaction_set_shortcut(self._ptr, qs)
vqt6_qstring_free(qs)
def setCheckable(self, checkable: int):
vqt6_qaction_set_checkable(self._ptr, checkable)
def isCheckable(self) -> int:
return vqt6_qaction_is_checkable(self._ptr)
def setChecked(self, checked: int):
vqt6_qaction_set_checked(self._ptr, checked)
def isChecked(self) -> int:
return vqt6_qaction_is_checked(self._ptr)
def setEnabled(self, enabled: int):
vqt6_qaction_set_enabled(self._ptr, enabled)
def isEnabled(self) -> int:
return vqt6_qaction_is_enabled(self._ptr)
def trigger(self):
vqt6_qaction_trigger(self._ptr)
def triggered(self, cb: bytes, data: bytes = 0):
vqt6_qaction_connect_triggered(self._ptr, cb, data)
@t.NoVTable
class QMenu:
"""QMenu 包装"""
_ptr: QtMenuPtr
def __init__(self, title: str = ""):
if title != "":
qs: QtStringPtr = vqt6_qstring_create(title)
self._ptr = vqt6_qmenu_create_with_title(qs)
vqt6_qstring_free(qs)
else:
self._ptr = vqt6_qmenu_create()
if self._ptr is None:
raise RuntimeError("Failed to create QMenu")
def addAction(self, action: t.CPtr):
vqt6_qmenu_add_action(self._ptr, action)
def addActionWithText(self, text: str) -> "QAction":
qs: QtStringPtr = vqt6_qstring_create(text)
ap: QtActionPtr = vqt6_qmenu_add_action_with_text(self._ptr, qs)
vqt6_qstring_free(qs)
a = QAction()
a._ptr = ap
return a
def addMenu(self, title: str) -> "QMenu":
qs: QtStringPtr = vqt6_qstring_create(title)
mp: QtMenuPtr = vqt6_qmenu_add_menu(self._ptr, qs)
vqt6_qstring_free(qs)
m = QMenu()
m._ptr = mp
return m
def addSeparator(self):
vqt6_qmenu_add_separator(self._ptr)
def setTitle(self, title: str):
qs: QtStringPtr = vqt6_qstring_create(title)
vqt6_qmenu_set_title(self._ptr, qs)
vqt6_qstring_free(qs)
def clear(self):
vqt6_qmenu_clear(self._ptr)
@t.NoVTable
class QMenuBar:
"""QMenuBar 包装"""
_ptr: QtMenuBarPtr
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qmenubar_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_qmenubar_create()
if self._ptr is None:
raise RuntimeError("Failed to create QMenuBar")
def addMenu(self, title: str) -> "QMenu":
qs: QtStringPtr = vqt6_qstring_create(title)
mp: QtMenuPtr = vqt6_qmenubar_add_menu(self._ptr, qs)
vqt6_qstring_free(qs)
m = QMenu()
m._ptr = mp
return m
def addAction(self, action: t.CPtr):
vqt6_qmenubar_add_action(self._ptr, action)
def clear(self):
vqt6_qmenubar_clear(self._ptr)
@t.NoVTable
class QStatusBar:
"""QStatusBar 包装"""
_ptr: QtStatusBarPtr
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qstatusbar_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_qstatusbar_create()
if self._ptr is None:
raise RuntimeError("Failed to create QStatusBar")
def showMessage(self, message: str, timeout_ms: int = 0):
qs: QtStringPtr = vqt6_qstring_create(message)
vqt6_qstatusbar_show_message(self._ptr, qs, timeout_ms)
vqt6_qstring_free(qs)
def clearMessage(self):
vqt6_qstatusbar_clear_message(self._ptr)
def currentMessage(self) -> str:
return _w_text_of(vqt6_qstatusbar_current_message(self._ptr))
def addWidget(self, w: t.CPtr, stretch: int = 0):
vqt6_qstatusbar_add_widget(self._ptr, w, stretch)
# ============================================================
# 窗口:QMainWindow(依赖上面的 QMenuBar / QStatusBar, 故排在其后)
# ============================================================
@t.NoVTable
class QMainWindow:
"""QMainWindow 包装"""
_ptr: QtMainWindowPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qmainwindow_create_with_parent(parent._ptr)
@@ -346,20 +549,16 @@ class QMainWindow:
def setCentralWidget(self, w: t.CPtr):
vqt6_qmainwindow_set_central_widget(self._ptr, w)
def menuBar(self) -> "QMenuBar":
mb_ptr: QtMenuBarPtr = vqt6_qmainwindow_menu_bar(self._ptr)
mb = QMenuBar()
mb._ptr = mb_ptr
return mb
def menuBar(self) -> QtMenuBarPtr:
"""返回窗口自带的菜单栏裸指针(不新建; 需要包装类请自行 new QMenuBar 后挂 _ptr)"""
return vqt6_qmainwindow_menu_bar(self._ptr)
def setMenuBar(self, mb: t.CPtr):
vqt6_qmainwindow_set_menu_bar(self._ptr, mb)
def statusBar(self) -> "QStatusBar":
sb_ptr: QtStatusBarPtr = vqt6_qmainwindow_status_bar(self._ptr)
sb = QStatusBar()
sb._ptr = sb_ptr
return sb
def statusBar(self) -> QtStatusBarPtr:
"""返回窗口自带的状态栏裸指针(不新建; 需要包装类请自行 new QStatusBar 后挂 _ptr)"""
return vqt6_qmainwindow_status_bar(self._ptr)
def addToolBar(self, tb: t.CPtr):
vqt6_qmainwindow_add_tool_bar(self._ptr, tb)
@@ -385,6 +584,9 @@ class QMainWindow:
def setWindowTitle(self, title: str):
_w_set_window_title(self._ptr, title)
def setStyleSheet(self, qss: str):
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
def setEnabled(self, enabled: int):
_w_set_enabled(self._ptr, enabled)
@@ -453,6 +655,9 @@ class QLineEdit:
"""QLineEdit 包装"""
_ptr: QtLineEditPtr
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, text: str = "", parent: "QWidget | None" = None):
qs: QtStringPtr = vqt6_qstring_create(text)
if parent is not None and parent._ptr is not None:
@@ -518,6 +723,9 @@ class QCheckBox:
"""QCheckBox 包装"""
_ptr: QtCheckBoxPtr
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, text: str = "", parent: "QWidget | None" = None):
qs: QtStringPtr = vqt6_qstring_create(text)
if parent is not None and parent._ptr is not None:
@@ -578,6 +786,9 @@ class QRadioButton:
"""QRadioButton 包装"""
_ptr: QtRadioButtonPtr
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, text: str = "", parent: "QWidget | None" = None):
qs: QtStringPtr = vqt6_qstring_create(text)
if parent is not None and parent._ptr is not None:
@@ -760,6 +971,9 @@ class QSpinBox:
"""QSpinBox 包装"""
_ptr: QtSpinBoxPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qspinbox_create_with_parent(parent._ptr)
@@ -826,6 +1040,9 @@ class QProgressBar:
"""QProgressBar 包装"""
_ptr: QtProgressBarPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qprogressbar_create_with_parent(parent._ptr)
@@ -857,6 +1074,9 @@ class QProgressBar:
def setTextVisible(self, visible: int):
vqt6_qprogressbar_set_text_visible(self._ptr, visible)
def setStyleSheet(self, qss: str):
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
def valueChanged(self, cb: bytes, data: bytes = 0):
vqt6_qprogressbar_connect_value_changed(self._ptr, cb, data)
@@ -946,6 +1166,9 @@ class QPlainTextEdit:
"""QPlainTextEdit 包装"""
_ptr: QtPlainTextEditPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qplaintextedit_create_with_parent(parent._ptr)
@@ -1226,6 +1449,9 @@ class QTabWidget:
"""QTabWidget 包装"""
_ptr: QtTabWidgetPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qtabwidget_create_with_parent(parent._ptr)
@@ -1553,6 +1779,9 @@ class QTimer:
"""QTimer 包装"""
_ptr: QtTimerPtr
def __new__(self) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self):
self._ptr = vqt6_qtimer_create()
if self._ptr is None:
@@ -1580,164 +1809,6 @@ class QTimer:
vqt6_qtimer_connect_timeout(self._ptr, cb, data)
# ============================================================
# 菜单:QMenuBar / QMenu / QAction / QStatusBar
# ============================================================
@t.NoVTable
class QMenuBar:
"""QMenuBar 包装"""
_ptr: QtMenuBarPtr
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qmenubar_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_qmenubar_create()
if self._ptr is None:
raise RuntimeError("Failed to create QMenuBar")
def addMenu(self, title: str) -> "QMenu":
qs: QtStringPtr = vqt6_qstring_create(title)
mp: QtMenuPtr = vqt6_qmenubar_add_menu(self._ptr, qs)
vqt6_qstring_free(qs)
m = QMenu()
m._ptr = mp
return m
def addAction(self, action: t.CPtr):
vqt6_qmenubar_add_action(self._ptr, action)
def clear(self):
vqt6_qmenubar_clear(self._ptr)
@t.NoVTable
class QMenu:
"""QMenu 包装"""
_ptr: QtMenuPtr
def __init__(self, title: str = ""):
if title != "":
qs: QtStringPtr = vqt6_qstring_create(title)
self._ptr = vqt6_qmenu_create_with_title(qs)
vqt6_qstring_free(qs)
else:
self._ptr = vqt6_qmenu_create()
if self._ptr is None:
raise RuntimeError("Failed to create QMenu")
def addAction(self, action: t.CPtr):
vqt6_qmenu_add_action(self._ptr, action)
def addActionWithText(self, text: str) -> "QAction":
qs: QtStringPtr = vqt6_qstring_create(text)
ap: QtActionPtr = vqt6_qmenu_add_action_with_text(self._ptr, qs)
vqt6_qstring_free(qs)
a = QAction()
a._ptr = ap
return a
def addMenu(self, title: str) -> "QMenu":
qs: QtStringPtr = vqt6_qstring_create(title)
mp: QtMenuPtr = vqt6_qmenu_add_menu(self._ptr, qs)
vqt6_qstring_free(qs)
m = QMenu()
m._ptr = mp
return m
def addSeparator(self):
vqt6_qmenu_add_separator(self._ptr)
def setTitle(self, title: str):
qs: QtStringPtr = vqt6_qstring_create(title)
vqt6_qmenu_set_title(self._ptr, qs)
vqt6_qstring_free(qs)
def clear(self):
vqt6_qmenu_clear(self._ptr)
@t.NoVTable
class QAction:
"""QAction 包装"""
_ptr: QtActionPtr
def __init__(self, text: str = ""):
if text != "":
qs: QtStringPtr = vqt6_qstring_create(text)
self._ptr = vqt6_qaction_create_with_text(qs)
vqt6_qstring_free(qs)
else:
self._ptr = vqt6_qaction_create()
if self._ptr is None:
raise RuntimeError("Failed to create QAction")
def setText(self, text: str):
qs: QtStringPtr = vqt6_qstring_create(text)
vqt6_qaction_set_text(self._ptr, qs)
vqt6_qstring_free(qs)
def text(self) -> str:
return _w_text_of(vqt6_qaction_text(self._ptr))
def setShortcut(self, key_seq: str):
qs: QtStringPtr = vqt6_qstring_create(key_seq)
vqt6_qaction_set_shortcut(self._ptr, qs)
vqt6_qstring_free(qs)
def setCheckable(self, checkable: int):
vqt6_qaction_set_checkable(self._ptr, checkable)
def isCheckable(self) -> int:
return vqt6_qaction_is_checkable(self._ptr)
def setChecked(self, checked: int):
vqt6_qaction_set_checked(self._ptr, checked)
def isChecked(self) -> int:
return vqt6_qaction_is_checked(self._ptr)
def setEnabled(self, enabled: int):
vqt6_qaction_set_enabled(self._ptr, enabled)
def isEnabled(self) -> int:
return vqt6_qaction_is_enabled(self._ptr)
def trigger(self):
vqt6_qaction_trigger(self._ptr)
def triggered(self, cb: bytes, data: bytes = 0):
vqt6_qaction_connect_triggered(self._ptr, cb, data)
@t.NoVTable
class QStatusBar:
"""QStatusBar 包装"""
_ptr: QtStatusBarPtr
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_qstatusbar_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_qstatusbar_create()
if self._ptr is None:
raise RuntimeError("Failed to create QStatusBar")
def showMessage(self, message: str, timeout_ms: int = 0):
qs: QtStringPtr = vqt6_qstring_create(message)
vqt6_qstatusbar_show_message(self._ptr, qs, timeout_ms)
vqt6_qstring_free(qs)
def clearMessage(self):
vqt6_qstatusbar_clear_message(self._ptr)
def currentMessage(self) -> str:
return _w_text_of(vqt6_qstatusbar_current_message(self._ptr))
def addWidget(self, w: t.CPtr, stretch: int = 0):
vqt6_qstatusbar_add_widget(self._ptr, w, stretch)
# ============================================================
# 绘图基础:QColor / QFont
# ============================================================
@@ -1798,6 +1869,278 @@ class QFont:
vqt6_qfont_set_underline(self._ptr, underline)
# ============================================================
# 剪贴板(复制链接等)
# ============================================================
def clipboard_set_text(text: str):
"""把文本写入系统剪贴板"""
vqt6_clipboard_set_text_utf8(text)
def clipboard_text() -> str:
"""读取系统剪贴板文本"""
buf: bytes = stdlib.malloc(4096)
vqt6_clipboard_text_utf8(buf, 4096)
s: str = buf
stdlib.free(buf)
return s
# ============================================================
# 自绘控件:Canvas / Graph3D / ChartView
# ============================================================
@t.NoVTable
class Canvas:
"""手绘板 / 28x28 灰度显示控件
手绘模式下鼠标左键描绘; toMnist28 把笔迹按 MNIST 预处理
(包围盒缩放到 20x20 后质心居中)输出为 28x28 灰度字节数组。
"""
_ptr: QtCanvasPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_canvas_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_canvas_create()
if self._ptr is None:
raise RuntimeError("Failed to create Canvas")
def clear(self):
vqt6_canvas_clear(self._ptr)
def hasInk(self) -> int:
return vqt6_canvas_has_ink(self._ptr)
def setMode(self, mode: int):
"""0 = 手绘, 1 = 图像显示"""
vqt6_canvas_set_mode(self._ptr, mode)
def mode(self) -> int:
return vqt6_canvas_mode(self._ptr)
def setPen(self, r: int, g: int, b: int, width_norm: t.CDouble):
vqt6_canvas_set_pen(self._ptr, r, g, b, width_norm)
def setGrid(self, on: int):
vqt6_canvas_set_grid(self._ptr, on)
def setScanLine(self, y_norm: int):
"""0..1000 扫描线位置, <0 关闭"""
vqt6_canvas_set_scan_line(self._ptr, y_norm)
def setBackground(self, r: int, g: int, b: int):
vqt6_canvas_set_bg(self._ptr, r, g, b)
def toMnist28(self, out: bytes) -> int:
"""笔迹 → 28x28 灰度(写入 out); 无墨迹返回 0"""
return vqt6_canvas_to_mnist28(self._ptr, out)
def setImage28(self, inp: bytes):
"""28x28 灰度 → 显示(自动切到图像显示模式)"""
vqt6_canvas_set_image28(self._ptr, inp)
def changed(self, cb: bytes, data: bytes = 0):
"""笔迹完成(鼠标抬起)回调: def cb(slot_id: int, data: t.CPtr)"""
vqt6_canvas_connect_changed(self._ptr, cb, data)
def show(self):
_w_show(self._ptr)
def hide(self):
_w_hide(self._ptr)
def setGeometry(self, x: int, y: int, w: int, h: int):
_w_set_geometry(self._ptr, x, y, w, h)
def resize(self, w: int, h: int):
_w_resize(self._ptr, w, h)
def move(self, x: int, y: int):
_w_move(self._ptr, x, y)
def setEnabled(self, enabled: int):
_w_set_enabled(self._ptr, enabled)
def setStyleSheet(self, qss: str):
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
@t.NoVTable
class Graph3D:
"""软件 3D 神经网络结构图控件
层次沿 Z 轴排布(输入层在最远端), 各层按近正方形网格铺开;
相邻层前向全连接。左键拖拽旋转, 滚轮缩放。
渲染 API 与后端无关, 将来换 OpenGL 实现时调用代码无需改动。
"""
_ptr: QtGraph3DPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_graph3d_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_graph3d_create()
if self._ptr is None:
raise RuntimeError("Failed to create Graph3D")
def setup(self, layer_sizes: t.CInt | t.CPtr, n_layers: int):
"""layer_sizes: int 数组指针(输入层→输出层各层节点数)"""
vqt6_graph3d_setup(self._ptr, layer_sizes, n_layers)
def clear(self):
vqt6_graph3d_clear(self._ptr)
def nodeCount(self) -> int:
return vqt6_graph3d_node_count(self._ptr)
def edgeCount(self) -> int:
return vqt6_graph3d_edge_count(self._ptr)
def setLayerAlpha(self, layer: int, alpha: t.CDouble):
"""逐层点亮: 0..1"""
vqt6_graph3d_set_layer_alpha(self._ptr, layer, alpha)
def setAllLayerAlpha(self, alpha: t.CDouble):
vqt6_graph3d_set_all_layer_alpha(self._ptr, alpha)
def setValues(self, values: t.CDouble | t.CPtr, n: int):
"""每节点激活值(0..1), 影响亮度与半径"""
vqt6_graph3d_set_values(self._ptr, values, n)
def setEdgeWeights(self, w: t.CDouble | t.CPtr, n: int):
"""边权重(按层间顺序), 影响颜色/透明度并按 |w| 裁剪"""
vqt6_graph3d_set_edge_weights(self._ptr, w, n)
def setEdgeThreshold(self, tval: t.CDouble):
vqt6_graph3d_set_edge_threshold(self._ptr, tval)
def setEdgeBudget(self, max_edges: int):
vqt6_graph3d_set_edge_budget(self._ptr, max_edges)
def setView(self, yaw_deg: t.CDouble, pitch_deg: t.CDouble, dist: t.CDouble):
vqt6_graph3d_set_view(self._ptr, yaw_deg, pitch_deg, dist)
def setNodeScale(self, s: t.CDouble):
vqt6_graph3d_set_node_scale(self._ptr, s)
def setBackground(self, r: int, g: int, b: int):
vqt6_graph3d_set_bg(self._ptr, r, g, b)
def resetView(self):
vqt6_graph3d_reset_view(self._ptr)
def view(self, yaw_deg: t.CDouble | t.CPtr, pitch_deg: t.CDouble | t.CPtr,
dist: t.CDouble | t.CPtr):
vqt6_graph3d_get_view(self._ptr, yaw_deg, pitch_deg, dist)
def viewChanged(self, cb: bytes, data: bytes = 0):
vqt6_graph3d_connect_view_changed(self._ptr, cb, data)
def show(self):
_w_show(self._ptr)
def hide(self):
_w_hide(self._ptr)
def setGeometry(self, x: int, y: int, w: int, h: int):
_w_set_geometry(self._ptr, x, y, w, h)
def resize(self, w: int, h: int):
_w_resize(self._ptr, w, h)
def move(self, x: int, y: int):
_w_move(self._ptr, x, y)
def setEnabled(self, enabled: int):
_w_set_enabled(self._ptr, enabled)
@t.NoVTable
class ChartView:
"""Qt Charts 折线图控件(右侧 Y 轴可选)
典型用法:
cv = ChartView()
loss = cv.addLine("loss", 255, 107, 107, 0)
acc = cv.addLine("acc", 77, 171, 247, 1)
cv.append(loss, step, loss_value)
cv.autoscale()
"""
_ptr: QtChartViewPtr
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
def __init__(self, parent: "QWidget | None" = None):
if parent is not None and parent._ptr is not None:
self._ptr = vqt6_chartview_create_with_parent(parent._ptr)
else:
self._ptr = vqt6_chartview_create()
if self._ptr is None:
raise RuntimeError("Failed to create ChartView")
def addLine(self, name: str, r: int, g: int, b: int, right_axis: int = 0) -> QtChartSeriesPtr:
return vqt6_chartview_add_line(self._ptr, name, r, g, b, right_axis)
def append(self, series: QtChartSeriesPtr, x: t.CDouble, y: t.CDouble):
vqt6_chartview_append(self._ptr, series, x, y)
def clearSeries(self, series: QtChartSeriesPtr):
vqt6_chartview_clear_series(self._ptr, series)
def clearAll(self):
vqt6_chartview_clear_all(self._ptr)
def autoscale(self):
vqt6_chartview_autoscale(self._ptr)
def setTitle(self, title: str):
vqt6_chartview_set_title(self._ptr, title)
def setLegendVisible(self, visible: int):
vqt6_chartview_set_legend_visible(self._ptr, visible)
def setBackground(self, r: int, g: int, b: int):
vqt6_chartview_set_background(self._ptr, r, g, b)
def setMaxPoints(self, max_points: int):
"""滑动窗口: 每条折线超过 max_points 就丢弃最旧的点(<=0 表示不限制)"""
vqt6_chartview_set_max_points(self._ptr, max_points)
def openZoom(self, title: str):
"""打开(或复用)大窗查看当前曲线; 关闭只是隐藏, 不销毁"""
vqt6_chartview_open_zoom(self._ptr, title)
def syncZoom(self):
"""把当前曲线与轴范围同步到大窗(大窗未打开时什么也不做)"""
vqt6_chartview_sync_zoom(self._ptr)
def show(self):
_w_show(self._ptr)
def hide(self):
_w_hide(self._ptr)
def setGeometry(self, x: int, y: int, w: int, h: int):
_w_set_geometry(self._ptr, x, y, w, h)
def resize(self, w: int, h: int):
_w_resize(self._ptr, w, h)
def move(self, x: int, y: int):
_w_move(self._ptr, x, y)
def setEnabled(self, enabled: int):
_w_set_enabled(self._ptr, enabled)
# ============================================================
# 模块导出
# ============================================================
@@ -1814,11 +2157,16 @@ __all__ = [
"QMessageBox", "QFileDialog", "QTimer",
"QMenuBar", "QMenu", "QAction", "QStatusBar",
"QColor", "QFont",
# 自绘控件
"Canvas", "Graph3D", "ChartView",
"clipboard_set_text", "clipboard_text",
# 类型
"QtObjectPtr", "QtStringPtr", "QtByteArrayPtr", "QtWidgetPtr", "QtAppPtr",
"QtLabelPtr", "QtPushButtonPtr",
"QtTreeWidgetPtr", "QtTreeWidgetItemPtr",
"QtColorPtr", "QtFontPtr", "QtPixmapPtr", "QtImagePtr", "QtPainterPtr",
"QtMenuBarPtr", "QtMenuPtr", "QtActionPtr", "QtStatusBarPtr",
"QtCanvasPtr", "QtGraph3DPtr", "QtChartViewPtr", "QtChartSeriesPtr",
"QtTcpSocketPtr", "QtHostAddressPtr", "QtNetworkRequestPtr", "QtNetworkReplyPtr",
# 常量
"QT_KEY_ESCAPE", "QT_KEY_RETURN", "QT_KEY_A", "QT_KEY_Z",
+7
View File
@@ -153,6 +153,13 @@ VQT6_API void vqt6_signal_unregister(int slot_id);
// ============================================================
VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_callback_t cb, void* user_data);
// ============================================================
// 剪贴板(复制链接等)
// ============================================================
VQT6_API void vqt6_clipboard_set_text_utf8(const char* utf8);
// 读取剪贴板文本到 buf(UTF-8); 返回写入字节数(不含结尾 NUL)
VQT6_API int vqt6_clipboard_text_utf8(char* buf, int buf_size);
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large. Load diff
+99
View File
@@ -0,0 +1,99 @@
// ============================================================
// vqt6/_bridge_canvas.h — 自绘控件 C 桥接声明
//
// 三个自绘控件(不依赖 Q_OBJECT/moc, 回调直接走 vqt6_trigger_slot):
// * Canvas — 手绘板 / 28x28 灰度图显示(带扫描线、网格)
// * Graph3D — 软件 3D 神经网络结构图(QPainter 透视投影)
// * ChartView — Qt Charts 折线图(loss/acc 曲线)
//
// 设计约定: Graph3D 的 API 与渲染后端无关(只暴露
// setup/set_values/set_edge_weights/set_layer_alpha/set_view),
// 将来若换成 OpenGL 后端, Viper 侧调用代码无需改动。
// ============================================================
#ifndef VQT6_BRIDGE_CANVAS_H
#define VQT6_BRIDGE_CANVAS_H
#include "_bridge.h"
#ifdef __cplusplus
extern "C" {
#endif
// ============================================================
// Canvas — 手绘板 / 28x28 灰度显示
// 坐标系约定: 内部统一用 0..1 归一化坐标, 与控件像素尺寸解耦
// ============================================================
VQT6_API void* vqt6_canvas_create(void);
VQT6_API void* vqt6_canvas_create_with_parent(void* parent);
VQT6_API void vqt6_canvas_clear(void* canvas);
VQT6_API int vqt6_canvas_has_ink(void* canvas);
VQT6_API void vqt6_canvas_set_mode(void* canvas, int mode); // 0=手绘 1=图像显示
VQT6_API int vqt6_canvas_mode(void* canvas);
VQT6_API void vqt6_canvas_set_pen(void* canvas, int r, int g, int b, double width_norm);
VQT6_API void vqt6_canvas_set_grid(void* canvas, int on); // 28x28 辅助网格
VQT6_API void vqt6_canvas_set_scan_line(void* canvas, int y_norm); // 0..1000, <0=关
VQT6_API void vqt6_canvas_set_bg(void* canvas, int r, int g, int b);
// 手绘内容 → 28x28 灰度(MNIST 预处理: 包围盒缩放到 20x20 后按质心居中); 无墨迹返回 0
VQT6_API int vqt6_canvas_to_mnist28(void* canvas, unsigned char* out);
// 28x28 灰度 → 显示(自动切到图像显示模式)
VQT6_API void vqt6_canvas_set_image28(void* canvas, const unsigned char* in);
// 笔迹完成(鼠标抬起)回调
VQT6_API void vqt6_canvas_connect_changed(void* canvas, vqt6_slot_callback_t cb, void* user_data);
// ============================================================
// Graph3D — 软件 3D 神经网络结构图
// 层布局: 每层按近正方形网格排在 XY 平面, 各层沿 Z 轴依次排列
// ============================================================
VQT6_API void* vqt6_graph3d_create(void);
VQT6_API void* vqt6_graph3d_create_with_parent(void* parent);
// layer_sizes: 自输入层到输出层各层节点数; 建立节点与前向全连接边
VQT6_API void vqt6_graph3d_setup(void* g, const int* layer_sizes, int n_layers);
VQT6_API void vqt6_graph3d_clear(void* g);
VQT6_API int vqt6_graph3d_node_count(void* g);
VQT6_API int vqt6_graph3d_edge_count(void* g);
// 逐层点亮: alpha 0..1(节点与相邻边同时生效)
VQT6_API void vqt6_graph3d_set_layer_alpha(void* g, int layer, double alpha);
VQT6_API void vqt6_graph3d_set_all_layer_alpha(void* g, double alpha);
// 每节点激活值(长度 = node_count), 取值 0..1, 影响节点亮度与半径
VQT6_API void vqt6_graph3d_set_values(void* g, const double* values, int n);
// 边权重(长度 = edge_count, 按层间顺序), 影响颜色/透明度, 并按 |w| 裁剪
VQT6_API void vqt6_graph3d_set_edge_weights(void* g, const double* w, int n);
VQT6_API void vqt6_graph3d_set_edge_threshold(void* g, double t); // |w|归一后低于此值不画
VQT6_API void vqt6_graph3d_set_edge_budget(void* g, int max_edges); // 最多画多少条(取 |w| 最大)
VQT6_API void vqt6_graph3d_set_view(void* g, double yaw_deg, double pitch_deg, double dist);
VQT6_API void vqt6_graph3d_get_view(void* g, double* yaw_deg, double* pitch_deg, double* dist);
VQT6_API void vqt6_graph3d_set_bg(void* g, int r, int gcol, int b);
VQT6_API void vqt6_graph3d_set_node_scale(void* g, double s);
VQT6_API void vqt6_graph3d_connect_view_changed(void* g, vqt6_slot_callback_t cb, void* user_data);
// 原点复位
VQT6_API void vqt6_graph3d_reset_view(void* g);
// ============================================================
// ChartView — Qt Charts 折线图(loss/acc)
// ============================================================
VQT6_API void* vqt6_chartview_create(void);
VQT6_API void* vqt6_chartview_create_with_parent(void* parent);
// 新增一条折线, 返回 series 句柄; right_axis!=0 时挂到右侧 Y 轴
VQT6_API void* vqt6_chartview_add_line(void* view, const char* name,
int r, int g, int b, int right_axis);
VQT6_API void vqt6_chartview_append(void* view, void* series, double x, double y);
VQT6_API void vqt6_chartview_clear_series(void* view, void* series);
VQT6_API void vqt6_chartview_clear_all(void* view);
// 按当前数据重算坐标轴范围(左轴/右轴各自独立)
VQT6_API void vqt6_chartview_autoscale(void* view);
VQT6_API void vqt6_chartview_set_title(void* view, const char* utf8);
VQT6_API void vqt6_chartview_set_legend_visible(void* view, int visible);
VQT6_API void vqt6_chartview_set_background(void* view, int r, int g, int b);
// 滑动窗口: 每条折线超过 max_points 时丢弃最旧的点(max_points<=0 表示不限制)
VQT6_API void vqt6_chartview_set_max_points(void* view, int max_points);
// 打开(或复用)只读大窗; 同一 view 只建一次, 关闭仅隐藏不销毁
VQT6_API void* vqt6_chartview_open_zoom(void* view, const char* title);
// 把源图当前曲线与轴范围同步到大窗(仅在大窗可见时生效)
VQT6_API void vqt6_chartview_sync_zoom(void* view);
#ifdef __cplusplus
}
#endif
#endif // VQT6_BRIDGE_CANVAS_H
+26
View File
@@ -7,6 +7,8 @@
#include <QByteArray>
#include <QObject>
#include <QCoreApplication>
#include <QGuiApplication>
#include <QClipboard>
#include <QTimer>
#include <QVariant>
#include <QEvent>
@@ -477,3 +479,27 @@ extern "C" VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_call
vqt6_trigger_slot(slot_id);
});
}
// ============================================================
// 剪贴板
// ============================================================
extern "C" VQT6_API void vqt6_clipboard_set_text_utf8(const char* utf8) {
QClipboard* cb = QGuiApplication::clipboard();
if (cb == nullptr || utf8 == nullptr) return;
cb->setText(QString::fromUtf8(utf8));
}
extern "C" VQT6_API int vqt6_clipboard_text_utf8(char* buf, int buf_size) {
if (buf == nullptr || buf_size <= 0) return 0;
QClipboard* cb = QGuiApplication::clipboard();
if (cb == nullptr) {
buf[0] = '\0';
return 0;
}
QByteArray u = cb->text().toUtf8();
int n = static_cast<int>(u.size());
if (n > buf_size - 1) n = buf_size - 1;
std::memcpy(buf, u.constData(), static_cast<size_t>(n));
buf[n] = '\0';
return n;
}
+133 -3
View File
@@ -171,6 +171,19 @@ extern "C" VQT6_API int vqt6_qapplication_quit_on_last_window_closed(void* app)
return QApplication::quitOnLastWindowClosed() ? 1 : 0;
}
extern "C" VQT6_API void vqt6_qapplication_set_font_family_utf8(void* app, const char* family) {
if (family == nullptr) return;
QApplication* a = (app != nullptr) ? static_cast<QApplication*>(app)
: static_cast<QApplication*>(QApplication::instance());
if (a == nullptr) return;
// 只换字体族:从当前应用字体克隆,字号/字重/斜体等全部保持不变。
// 不能直接 QFont(family) —— 那个构造把 pointSize 置为 -1,实际字号会被
// 平台默认值改写,整个界面的字号会跟着变。
QFont f = QApplication::font();
f.setFamily(QString::fromUtf8(family));
QApplication::setFont(f);
}
// ============================================================
// QWidget
// ============================================================
@@ -1167,18 +1180,135 @@ extern "C" VQT6_API void vqt6_enable_custom_item_checks(void* app) {
a->setStyle(new vqt6_CheckProxyStyle());
}
// ------------------------------------------------------------------
// 自绘 QRadioButton:圆形指示器(圆环 + 内点)
// 与自绘 QCheckBox 同源:同样的 QPalette 取色、同样的 16ms 指数衰减过渡
// (参考 ViperQtProject 里 vqt6_CheckBox / vqt6_CheckProxyStyle 的做法)。
// ------------------------------------------------------------------
// 圆形的绘制区域:在 r 内取居中的正方形(半径一致才是个正圆)
static QRectF vqt6_radio_box(const QRect& r) {
qreal side = qMin(r.width(), r.height()) - 2.0;
if (side < 10.0) side = 14.0;
QRectF box(0, 0, side, side);
box.moveCenter(QRectF(r).center());
return box;
}
// 统一的单选指示器绘制:圆环 + 随进度长大的内点
// box —— 绘制区域(正方形)
// pal —— 控件调色板(决定深浅主题配色)
// enabled —— 是否可用
// progress —— 选中过渡进度(0=未选 1=选中)
static void vqt6_paint_radio_indicator(QPainter* p, const QRectF& box, const QPalette& pal,
bool enabled, qreal progress) {
p->setRenderHint(QPainter::Antialiasing, true);
const qreal t = progress < 0.0 ? 0.0 : (progress > 1.0 ? 1.0 : progress);
const QColor cBase = enabled ? pal.base().color() : pal.window().color();
const QColor cOn = enabled ? pal.highlight().color() : pal.button().color();
const QColor cB0 = enabled ? pal.text().color() : pal.placeholderText().color();
const QColor cB1 = enabled ? pal.highlight().color() : pal.buttonText().color();
const QColor fill = vqt6_mix_color(cBase, cOn, t);
const QColor border = vqt6_mix_color(cB0, cB1, t);
QPainterPath path;
path.addEllipse(box);
p->fillPath(path, fill);
QPen bpen(border, 1.4);
p->setPen(bpen);
p->drawEllipse(box);
if (!enabled || t <= 0.003) return;
// 内点:半径从 0 涨到 0.32*边长,颜色取 highlightedText 并按进度淡入
QColor gc = pal.highlightedText().color();
gc.setAlphaF(gc.alphaF() * t);
const qreal side = qMin(box.width(), box.height());
const qreal rd = side * 0.32 * t;
p->setPen(Qt::NoPen);
p->setBrush(gc);
p->drawEllipse(box.center(), rd, rd);
}
class vqt6_RadioButton : public QRadioButton {
public:
vqt6_RadioButton(QWidget* parent = nullptr) : QRadioButton(parent) {
initAnim();
}
vqt6_RadioButton(const QString& text, QWidget* parent = nullptr)
: QRadioButton(text, parent) {
initAnim();
}
protected:
void paintEvent(QPaintEvent*) override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
const QPalette& pal = palette();
QStyleOptionButton opt;
initStyleOption(&opt);
QRect r = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &opt, this);
if (r.isEmpty() || r.isNull()) {
r = QRect(0, 0, 16, 16);
r.moveCenter(QPoint(rect().left() + 12, rect().center().y()));
}
// 圆环 + 内点:与自绘复选框共用配色与动画进度
vqt6_paint_radio_indicator(&p, vqt6_radio_box(r), pal, isEnabled(), m_progress);
// 文本
QRect lr = style()->subElementRect(QStyle::SE_RadioButtonContents, &opt, this);
if (!lr.isEmpty() && !lr.isNull()) {
p.setPen(QPen(isEnabled() ? pal.text() : pal.placeholderText(), 1.0));
p.drawText(lr, Qt::AlignLeft | Qt::AlignVCenter, text());
}
}
void showEvent(QShowEvent* e) override {
snapToState();
QRadioButton::showEvent(e);
}
private:
QTimer m_timer;
qreal m_progress = 0.0;
qreal m_target = 0.0;
void initAnim() {
m_timer.setInterval(16);
QObject::connect(&m_timer, &QTimer::timeout, this, [this]() {
m_progress += (m_target - m_progress) * 0.25;
if (qAbs(m_target - m_progress) < 0.005) {
m_progress = m_target;
m_timer.stop();
}
update();
});
QObject::connect(this, &QRadioButton::toggled, this, [this](bool on) {
m_target = on ? 1.0 : 0.0;
if (!isVisible()) {
m_progress = m_target;
} else if (!m_timer.isActive()) {
m_timer.start();
}
});
}
void snapToState() {
m_target = isChecked() ? 1.0 : 0.0;
m_progress = m_target;
m_timer.stop();
}
};
extern "C" VQT6_API void* vqt6_qradiobutton_create(void) {
return new QRadioButton();
return new vqt6_RadioButton();
}
extern "C" VQT6_API void* vqt6_qradiobutton_create_with_text(void* text) {
if (text == nullptr) return nullptr;
return new QRadioButton(*static_cast<QString*>(text));
return new vqt6_RadioButton(*static_cast<QString*>(text));
}
extern "C" VQT6_API void* vqt6_qradiobutton_create_with_text_parent(void* text, void* parent) {
if (text == nullptr || parent == nullptr) return nullptr;
return new QRadioButton(*static_cast<QString*>(text), static_cast<QWidget*>(parent));
return new vqt6_RadioButton(*static_cast<QString*>(text), static_cast<QWidget*>(parent));
}
extern "C" VQT6_API void vqt6_qradiobutton_set_checked(void* rb, int checked) {
+1
View File
@@ -25,6 +25,7 @@ VQT6_API int vqt6_qapplication_desktop_height(void* app);
VQT6_API void vqt6_qapplication_set_quit_on_last_window_closed(void* app, int quit);
VQT6_API int vqt6_qapplication_quit_on_last_window_closed(void* app);
VQT6_API void vqt6_qapplication_set_theme_palette(void* app, int theme);
VQT6_API void vqt6_qapplication_set_font_family_utf8(void* app, const char* family);
VQT6_API void vqt6_enable_custom_item_checks(void* app);
// ============================================================
+7
View File
@@ -158,3 +158,10 @@ def vqt6_qbytearraylist_free(list: QtByteArrayListPtr) -> t.State: pass
def vqt6_register_slot(slot_id: t.CInt, user_data: t.CVoid | t.CPtr) -> t.State: pass
def vqt6_unregister_slot(slot_id: t.CInt) -> t.State: pass
def vqt6_trigger_slot(slot_id: t.CInt) -> t.State: pass # 测试用
# ============================================================
# 剪贴板 — 复制链接等
# ============================================================
def vqt6_clipboard_set_text_utf8(text: t.CChar | t.CPtr) -> t.State: pass
def vqt6_clipboard_text_utf8(buf: t.CChar | t.CPtr, buf_size: t.CInt) -> t.CInt | t.State: pass
+62
View File
@@ -21,6 +21,7 @@ def vqt6_qapplication_desktop_height(app: QtAppPtr) -> t.CInt | t.State: pass
def vqt6_qapplication_set_quit_on_last_window_closed(app: QtAppPtr, quit: t.CInt) -> t.State: pass
def vqt6_qapplication_quit_on_last_window_closed(app: QtAppPtr) -> t.CInt | t.State: pass
def vqt6_qapplication_set_theme_palette(app: QtAppPtr, theme: t.CInt) -> t.State: pass
def vqt6_qapplication_set_font_family_utf8(app: QtAppPtr, family: t.CChar | t.CPtr) -> t.State: pass
def vqt6_enable_custom_item_checks(app: QtAppPtr) -> t.State: pass
@@ -546,3 +547,64 @@ def vqt6_qtabwidget_connect_current_changed(tw: QtTabWidgetPtr, cb: bytes, user_
def vqt6_qaction_connect_triggered(action: QtActionPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qtreewidget_connect_item_changed(tw: QtTreeWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_qtreewidget_connect_item_clicked(tw: QtTreeWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
# ============================================================
# Canvas — 手绘板 / 28x28 灰度显示(自绘控件)
# 坐标约定: 内部 0..1 归一化, 与控件像素尺寸解耦
# ============================================================
def vqt6_canvas_create() -> QtCanvasPtr | t.State: pass
def vqt6_canvas_create_with_parent(parent: QtWidgetPtr) -> QtCanvasPtr | t.State: pass
def vqt6_canvas_clear(canvas: QtCanvasPtr) -> t.State: pass
def vqt6_canvas_has_ink(canvas: QtCanvasPtr) -> t.CInt | t.State: pass
def vqt6_canvas_set_mode(canvas: QtCanvasPtr, mode: t.CInt) -> t.State: pass
def vqt6_canvas_mode(canvas: QtCanvasPtr) -> t.CInt | t.State: pass
def vqt6_canvas_set_pen(canvas: QtCanvasPtr, r: t.CInt, g: t.CInt, b: t.CInt, width_norm: t.CDouble) -> t.State: pass
def vqt6_canvas_set_grid(canvas: QtCanvasPtr, on: t.CInt) -> t.State: pass
def vqt6_canvas_set_scan_line(canvas: QtCanvasPtr, y_norm: t.CInt) -> t.State: pass
def vqt6_canvas_set_bg(canvas: QtCanvasPtr, r: t.CInt, g: t.CInt, b: t.CInt) -> t.State: pass
def vqt6_canvas_to_mnist28(canvas: QtCanvasPtr, out: t.CUnsignedChar | t.CPtr) -> t.CInt | t.State: pass
def vqt6_canvas_set_image28(canvas: QtCanvasPtr, inp: t.CUnsignedChar | t.CPtr) -> t.State: pass
def vqt6_canvas_connect_changed(canvas: QtCanvasPtr, cb: bytes, user_data: bytes) -> t.State: pass
# ============================================================
# Graph3D — 软件 3D 神经网络结构图(自绘控件)
# API 与渲染后端无关, 后续可替换为 OpenGL 实现
# ============================================================
def vqt6_graph3d_create() -> QtGraph3DPtr | t.State: pass
def vqt6_graph3d_create_with_parent(parent: QtWidgetPtr) -> QtGraph3DPtr | t.State: pass
def vqt6_graph3d_setup(g: QtGraph3DPtr, layer_sizes: t.CInt | t.CPtr, n_layers: t.CInt) -> t.State: pass
def vqt6_graph3d_clear(g: QtGraph3DPtr) -> t.State: pass
def vqt6_graph3d_node_count(g: QtGraph3DPtr) -> t.CInt | t.State: pass
def vqt6_graph3d_edge_count(g: QtGraph3DPtr) -> t.CInt | t.State: pass
def vqt6_graph3d_set_layer_alpha(g: QtGraph3DPtr, layer: t.CInt, alpha: t.CDouble) -> t.State: pass
def vqt6_graph3d_set_all_layer_alpha(g: QtGraph3DPtr, alpha: t.CDouble) -> t.State: pass
def vqt6_graph3d_set_values(g: QtGraph3DPtr, values: t.CDouble | t.CPtr, n: t.CInt) -> t.State: pass
def vqt6_graph3d_set_edge_weights(g: QtGraph3DPtr, w: t.CDouble | t.CPtr, n: t.CInt) -> t.State: pass
def vqt6_graph3d_set_edge_threshold(g: QtGraph3DPtr, tval: t.CDouble) -> t.State: pass
def vqt6_graph3d_set_edge_budget(g: QtGraph3DPtr, max_edges: t.CInt) -> t.State: pass
def vqt6_graph3d_set_view(g: QtGraph3DPtr, yaw_deg: t.CDouble, pitch_deg: t.CDouble, dist: t.CDouble) -> t.State: pass
def vqt6_graph3d_get_view(g: QtGraph3DPtr, yaw_deg: t.CDouble | t.CPtr, pitch_deg: t.CDouble | t.CPtr, dist: t.CDouble | t.CPtr) -> t.State: pass
def vqt6_graph3d_set_bg(g: QtGraph3DPtr, r: t.CInt, gcol: t.CInt, b: t.CInt) -> t.State: pass
def vqt6_graph3d_set_node_scale(g: QtGraph3DPtr, s: t.CDouble) -> t.State: pass
def vqt6_graph3d_connect_view_changed(g: QtGraph3DPtr, cb: bytes, user_data: bytes) -> t.State: pass
def vqt6_graph3d_reset_view(g: QtGraph3DPtr) -> t.State: pass
# ============================================================
# ChartView — Qt Charts 折线图(loss/acc 曲线)
# ============================================================
def vqt6_chartview_create() -> QtChartViewPtr | t.State: pass
def vqt6_chartview_create_with_parent(parent: QtWidgetPtr) -> QtChartViewPtr | t.State: pass
def vqt6_chartview_add_line(view: QtChartViewPtr, name: t.CChar | t.CPtr, r: t.CInt, g: t.CInt, b: t.CInt, right_axis: t.CInt) -> QtChartSeriesPtr | t.State: pass
def vqt6_chartview_append(view: QtChartViewPtr, series: QtChartSeriesPtr, x: t.CDouble, y: t.CDouble) -> t.State: pass
def vqt6_chartview_clear_series(view: QtChartViewPtr, series: QtChartSeriesPtr) -> t.State: pass
def vqt6_chartview_clear_all(view: QtChartViewPtr) -> t.State: pass
def vqt6_chartview_autoscale(view: QtChartViewPtr) -> t.State: pass
def vqt6_chartview_set_title(view: QtChartViewPtr, title: t.CChar | t.CPtr) -> t.State: pass
def vqt6_chartview_set_legend_visible(view: QtChartViewPtr, visible: t.CInt) -> t.State: pass
def vqt6_chartview_set_background(view: QtChartViewPtr, r: t.CInt, g: t.CInt, b: t.CInt) -> t.State: pass
def vqt6_chartview_set_max_points(view: QtChartViewPtr, max_points: t.CInt) -> t.State: pass
def vqt6_chartview_open_zoom(view: QtChartViewPtr, title: t.CChar | t.CPtr) -> QtWidgetPtr | t.State: pass
def vqt6_chartview_sync_zoom(view: QtChartViewPtr) -> t.State: pass
+6
View File
@@ -80,6 +80,12 @@ QtColorDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QColorDialog*
QtFontDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QFontDialog*
QtInputDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QInputDialog*
# vqt6 自绘控件(_bridge_canvas)
QtCanvasPtr: t.CTypedef = t.CVoid | t.CPtr # Vqt6Canvas*(QWidget* 兼容)
QtGraph3DPtr: t.CTypedef = t.CVoid | t.CPtr # Vqt6Graph3D*(QWidget* 兼容)
QtChartViewPtr: t.CTypedef = t.CVoid | t.CPtr # QChartView*
QtChartSeriesPtr: t.CTypedef = t.CVoid | t.CPtr # QLineSeries*
# QtNetwork
QtTcpSocketPtr: t.CTypedef = t.CVoid | t.CPtr # QTcpSocket*
QtUdpSocketPtr: t.CTypedef = t.CVoid | t.CPtr # QUdpSocket*
+50 -13
View File
@@ -1,6 +1,7 @@
import t, c
from stdint import *
import stdio
import stdlib
import w32.win32base
import w32.win32file
@@ -36,6 +37,26 @@ SHARE_WRITE: t.CDefine = 0x00000002
SHARE_DELETE: t.CDefine = 0x00000004
def create_file_utf8(path: str, access: ULONG, share: ULONG, disposition: ULONG,
flags: ULONG) -> w32.win32base.HANDLE:
"""按路径编码打开/创建文件。
合法 UTF-8(源码字面量、Qt 返回的中文目录) → 转 UTF-16 走 CreateFileW;
非法 UTF-8(如命令行 argv 的 ANSI 串) → 退回 CreateFileA。详见 win32base"""
if w32.win32base.utf8_is_valid(path) == 0:
return w32.win32file.CreateFileA(path, access, share, None, disposition,
flags, None)
wp: w32.win32base.WCHARPTR = w32.win32base.utf8_to_utf16_dup(path)
if wp is None:
# 宽字符缓冲分配失败: 走 A 路径, 非 ASCII 路径在此自然失败并返回
# INVALID_HANDLE_VALUE(不掩盖错误, 只是复用同一失败出口)
return w32.win32file.CreateFileA(path, access, share, None, disposition,
flags, None)
h: w32.win32base.HANDLE = w32.win32file.CreateFileW(
t.CPtr(wp), access, share, None, disposition, flags, None)
stdlib.free(wp)
return h
class File:
handle: w32.win32base.HANDLE
closed: bool
@@ -97,9 +118,9 @@ class File:
disposition = w32.win32file.OPEN_EXISTING
self.can_read = True
self.handle = w32.win32file.CreateFileA(
filename, access, self._share_mode,
None, disposition, w32.win32file.FILE_ATTRIBUTE_NORMAL, None
self.handle = create_file_utf8(
filename, access, self._share_mode, disposition,
w32.win32file.FILE_ATTRIBUTE_NORMAL
)
if self.handle == w32.win32base.INVALID_HANDLE_VALUE:
@@ -189,22 +210,30 @@ class File:
buf[total] = 0
return LONG(total)
def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
def read_all(self, buf: bytes, buf_size: ULONG) -> LONG:
"""把整个文件读进 buf 并写成 C 字符串(内部先 seek 到 0)
契约(注意与 write 的 count 口径不同):
buf_size 是缓冲区**容量, 含终止符** —— 最多读 buf_size-1 字节,
再在 buf[返回值] 写一个 NUL 字节, 故返回值 <= buf_size-1。
文件比 buf_size-1 更长时**静默截断**, 不报错; 要区分"读完了"还是
"被截断了", 请在调用前自己比一次 f.size()。
返回实际读到的字节数, 负数为错误码。"""
if self.closed: return FRESULT.ERR_CLOSED
if not self.can_read: return FRESULT.ERR_PERM
if max_count < 2: return FRESULT.ERR
if buf_size < 2: return FRESULT.ERR
file_size: LONGLONG = self.size()
if file_size < 0: return FRESULT.ERR_IO
to_read: ULONG = 0
if file_size < max_count - 1:
if file_size < buf_size - 1:
to_read = ULONG(file_size)
else:
to_read = max_count - 1
to_read = buf_size - 1
self.seek(0, SEEK_SET)
bytes_read: ULONG = 0
result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
if result == 0: return FRESULT.ERR_IO
if bytes_read < max_count:
if bytes_read < buf_size:
buf[bytes_read] = 0
return LONG(bytes_read)
@@ -362,22 +391,30 @@ class FileW:
buf[total] = 0
return LONG(total)
def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
def read_all(self, buf: bytes, buf_size: ULONG) -> LONG:
"""把整个文件读进 buf 并写成 C 字符串(内部先 seek 到 0)
契约(注意与 write 的 count 口径不同):
buf_size 是缓冲区**容量, 含终止符** —— 最多读 buf_size-1 字节,
再在 buf[返回值] 写一个 NUL 字节, 故返回值 <= buf_size-1。
文件比 buf_size-1 更长时**静默截断**, 不报错; 要区分"读完了"还是
"被截断了", 请在调用前自己比一次 f.size()。
返回实际读到的字节数, 负数为错误码。"""
if self.closed: return FRESULT.ERR_CLOSED
if not self.can_read: return FRESULT.ERR_PERM
if max_count < 2: return FRESULT.ERR
if buf_size < 2: return FRESULT.ERR
file_size: LONGLONG = self.size()
if file_size < 0: return FRESULT.ERR_IO
to_read: ULONG = 0
if file_size < max_count - 1:
if file_size < buf_size - 1:
to_read = ULONG(file_size)
else:
to_read = max_count - 1
to_read = buf_size - 1
self.seek(0, SEEK_SET)
bytes_read: ULONG = 0
result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
if result == 0: return FRESULT.ERR_IO
if bytes_read < max_count:
if bytes_read < buf_size:
buf[bytes_read] = 0
return LONG(bytes_read)
+42
View File
@@ -1,5 +1,6 @@
import t
from stdint import *
import stdlib
HANDLE: t.CTypedef = VOIDPTR
LPCSTR: t.CTypedef = t.CConst | t.CChar | t.CPtr
@@ -78,3 +79,44 @@ extern def SleepEx(dwMilliseconds: ULONG, bAlertable: BOOL) -> ULONG
extern def GetTickCount() -> ULONG
extern def GetTickCount64() -> ULONGLONG
extern def GetCommandLineA() -> CHARPTR
# =============================================================================
# 路径编码: Viper 字符串统一 UTF-8, 而 Windows 的 A 系列 API 走系统 ANSI 代码页
# (中文机器上为 GBK)。直接拿 UTF-8 路径调 CreateFileA / CreateDirectoryA,
# 遇到中文目录名会被按 GBK 误读 → 误报"文件/目录不存在"。故路径统一转 UTF-16
# 走 W 系列 API; 仅当字节串不是合法 UTF-8(如命令行 argv 传来的 ANSI 串)时才
# 退回 A 系列, 保持既有行为。
# =============================================================================
UTF8_CODEPAGE: t.CDefine = 65001
MB_ERR_INVALID_CHARS: t.CDefine = 0x00000008
extern def MultiByteToWideChar(CodePage: UINT, dwFlags: DWORD, lpMultiByteStr: LPCSTR,
cbMultiByte: int, lpWideCharStr: WCHARPTR,
cchWideChar: int) -> int
def utf8_is_valid(s: str) -> int:
"""整串(含结尾 NUL)是否为合法 UTF-8。1=是(按 UTF-8 处理), 0=否(按 ANSI)"""
if s is None:
return 0
n: int = MultiByteToWideChar(UTF8_CODEPAGE, MB_ERR_INVALID_CHARS, s, -1,
t.CPtr(0), 0)
if n <= 0:
return 0
return 1
def utf8_to_utf16_dup(s: str) -> WCHARPTR:
"""UTF-8 → 新分配的 UTF-16 缓冲(含结尾 NUL), 调用方负责 stdlib.free;
失败(含非法 UTF-8)返回 None"""
if s is None:
return None
n: int = MultiByteToWideChar(UTF8_CODEPAGE, 0, s, -1, t.CPtr(0), 0)
if n <= 0:
return None
buf: WCHARPTR = stdlib.malloc(t.CSizeT(n) * 2)
if buf is None:
return None
MultiByteToWideChar(UTF8_CODEPAGE, 0, s, -1, buf, n)
return buf