diff --git a/CHANGELOG-ZH.md b/CHANGELOG-ZH.md index 153257f..8c4b0da 100644 --- a/CHANGELOG-ZH.md +++ b/CHANGELOG-ZH.md @@ -1,3 +1,20 @@ +## 0.6.9 + +- Matlab `sparse(A)` 现可接受静态 shape 的 logical rank-2 数组,并产生类型化 logical CSC 矩阵。 +- 推断 shape、显式 shape 与 `nzmax` 三类 R2024 triplet 构造形式现均支持 logical value。 +- 重复 logical triplet 坐标按照 Matlab 语义使用 `any` 聚合,不再执行数值加法。 +- false logical value 不会作为显式 entry 保留在 canonical CSC 存储中。 +- 稀疏普通转置与共轭转置现可保持 logical 值类型。 +- scalar、linear 与 Cartesian submatrix selection 会在适用结果上保持 logical sparse 存储和类型。 +- indexed assignment、扩容与删除会保持 logical CSC;写入 false 会删除对应存储项。 +- sparse reshape 会保持 logical value、Matlab 列主序、推断维度和零 extent。 +- logical sparse 输入经 `full` 转换后返回 logical dense 数组,`issparse` 与 `nnz` 保持预期行为。 +- 生成的 JavaScript 为 sparse value domain 携带显式 tag,并在使用前拒绝损坏的 logical CSC value。 +- 生成的 C++17 使用 `sparse_matrix` 表示 logical CSC,并安全处理压缩 logical vector 存储。 +- JavaScript 与 C++17 使用彼此独立的类型化 sparse runtime,不要求先生成另一目标。 +- 修复引入类型化 sparse conversion ABI 后,sparse RHS 方阵求解的转换错误。 +- sparse assignment 失败现在会在生成的 JavaScript 与 C++17 中使用一致的 real-or-logical 诊断。 + ## 0.6.8 - Matlab 稀疏矩阵现可在已支持的静态 finite-real rank-2 能力中完整保持零 extent。 @@ -13,8 +30,6 @@ - 双目标 LIR v34 新增显式 runtime shape 调用 ABI,使 renderer 只序列化已验证调用,不再恢复 semantic storage 或 shape policy。 - source map 会保留全部新增稀疏操作以及零维左除、右除的原始调用位置。 - 新增零 extent sparse 的双目标差分执行、生成计划拒错、跨层损坏、fuzz 与架构覆盖。 -- 验证基线现包含 268 项 C++ 测试、98 个差分 case、127 项 CTest 和 18 项生成 runtime 拒错测试。 -- 生产源码覆盖率为 91.69%(37,154/40,520 行),既有 sparse-solve 性能预算无需放宽即可通过。 ## 0.6.7 @@ -29,10 +44,8 @@ - source map 会为全部稀疏 operand 组合保留原始 `.*` 表达式位置。 - complex、零 extent、动态 shape 或不兼容的稀疏逐元素运算继续失败关闭,不会静默改变语义。 - 新增可执行 Matlab 示例,在两个输出目标间比较数值、shape 与稀疏存储。 -- 验证基线现包含 258 项 C++ 测试、97 个差分 case、124 项 CTest 和 16 项生成 runtime 拒错测试。 - Matlab fuzz corpus 现覆盖稀疏逐元素语法、操作数方向、广播与 runtime 验证边界。 - 新增独立 sparse element-wise benchmark,约束编译延迟、吞吐、arena 峰值与生成代码大小。 -- 生产源码覆盖率为 91.59%(36,732/40,103 行),高于 85% 硬门槛。 ## 0.6.6 @@ -110,7 +123,7 @@ - 跨层 verifier 会在发射前拒绝损坏的 sparse-index identity、arity、shape、type、storage 或 inactive-state fact。 - 当 storage representation 已知时,`full` 现可接受结果 extent 为动态值或零值的 rank-two dense/CSC selection。 - sparse assignment、超过两个 selector、complex/sparse selector、N 维 linear result,以及动态、空或 complex sparse source 继续以稳定诊断失败关闭。 -- 新增可执行 sparse-indexing 示例,覆盖双目标行为、source-map 保留、越界拒绝与 fuzz 回归;生产代码行覆盖率为 91.24%(33,780/37,023)。 +- 新增可执行 sparse-indexing 示例,覆盖双目标行为、source-map 保留、越界拒绝与 fuzz 回归。 ## 0.6.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4392a..87bd8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.6.9 + +- Matlab `sparse(A)` now accepts statically shaped logical rank-two arrays and produces typed logical CSC matrices. +- Logical triplet constructors are supported across the inferred, explicitly sized, and `nzmax` R2024 call forms. +- Duplicate logical triplets now follow Matlab semantics by combining values with `any` instead of numeric addition. +- False logical values are omitted from canonical CSC storage rather than retained as explicit entries. +- Ordinary and conjugating sparse transpose preserve the logical value class. +- Scalar, linear, and Cartesian submatrix selection preserve logical sparse storage and result type where applicable. +- Indexed assignment, growth, and deletion preserve logical CSC matrices, with false assignments removing stored entries. +- Sparse reshape preserves logical values, Matlab column-major order, inferred dimensions, and zero extents. +- `full` returns logical dense arrays for logical sparse inputs, while `issparse` and `nnz` retain their expected behavior. +- Generated JavaScript carries an explicit sparse value-domain tag and rejects malformed logical CSC values before use. +- Generated C++17 uses `sparse_matrix` for logical CSC values and safely handles packed logical-vector storage. +- JavaScript and C++17 use independent typed sparse runtimes and do not require one target to be generated before the other. +- Fixed sparse square solves with sparse right-hand sides after introducing the typed sparse conversion ABI. +- Sparse assignment failures now use consistent real-or-logical diagnostics across generated JavaScript and C++17. + ## 0.6.8 - Matlab sparse matrices now preserve zero extents throughout the supported static, finite-real rank-two feature set. @@ -13,8 +30,6 @@ - Target LIR v34 now carries an explicit runtime shape-call ABI, allowing renderers to serialize validated calls without recovering semantic storage or shape policy. - Source maps preserve every new sparse operation and zero-dimensional left- or right-division call site. - Added dual-target differential execution, generated-plan rejection, cross-layer corruption, fuzz, and architecture coverage for zero-extent sparse behavior. -- The validation baseline now contains 268 C++ tests, 98 differential cases, 127 CTest entries, and 18 generated-runtime rejection tests. -- Production source coverage is 91.69% (37,154 of 40,520 lines), and the existing sparse-solve performance budget passes without being widened. ## 0.6.7 @@ -29,10 +44,8 @@ - Source maps preserve the original `.*` expression location for every sparse operand arrangement. - Complex, zero-extent, dynamically shaped, and incompatible sparse element-wise operations continue to fail closed with diagnostics instead of changing semantics. - Added an executable Matlab example that compares values, shape, and sparse storage across both output targets. -- The validation baseline now contains 258 C++ tests, 97 differential cases, 124 CTest entries, and 16 generated-runtime rejection tests. - The Matlab fuzz corpus now exercises sparse element-wise syntax, operand directions, broadcasting, and runtime validation boundaries. - A dedicated sparse element-wise benchmark constrains compilation latency, throughput, peak arena memory, and generated-code size. -- Production source coverage is 91.59% (36,732 of 40,103 lines), above the 85% required threshold. ## 0.6.6 @@ -110,7 +123,7 @@ - Cross-layer verifiers reject corrupted sparse-index identity, arity, shape, type, storage, or inactive-state facts before emission. - `full` now accepts rank-two dense or CSC selections with dynamic or zero result extents when their storage representation is known. - Sparse assignment, more than two selectors, complex or sparse selectors, N-dimensional linear results, and dynamic, empty, or complex sparse sources continue to fail closed with stable diagnostics. -- Added an executable sparse-indexing example with dual-target behavior, source-map preservation, out-of-bounds rejection, and fuzz regression coverage; production line coverage is 91.24% (33,780/37,023). +- Added an executable sparse-indexing example with dual-target behavior, source-map preservation, out-of-bounds rejection, and fuzz regression coverage. ## 0.6.1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 87cbfb3..7df643b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.20) project( mpf - VERSION 0.6.8 + VERSION 0.6.9 DESCRIPTION "Modern, high-performance multilingual translation framework." LANGUAGES C CXX) diff --git a/README-ZH.md b/README-ZH.md index bd1c102..0d408e0 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -24,7 +24,7 @@ | 输入语言 | 自动识别扩展名 | 当前能力摘要 | |---|---|---| -| Matlab | `.m` | 脚本与局部函数、条件/循环/标量 `switch`、N 维逐元素 `~`/`&`/`|`、标量短路 `&&`/`||`、数组 condition truthiness、保持 shape 的 `all`/`any` 逻辑归约、实数与复数标量/数组、保持 shape 的 `0×0` 与零 extent 数组、静态 N 维及 local-function runtime-shape 隐式扩展、数组比较、静态及运行时 extent 的 `end`、保序/重复/空 numeric selector、线性/逐维 logical selector、vector/matrix/N 维扩容与单轴删除、普通/共轭转置、section、`reshape`、复数逐元素算术、二维实数/复数矩阵乘法、结构感知实数方阵求解、Hermitian 正定/稠密复数方阵求解、rank-aware 实数/复数矩形求解、R2024 全部 `sparse` 调用形式在静态 real rank-2 CSC contract(包含零 extent)内的实现、稀疏 scalar/linear/submatrix selection、indexed assignment、扩容、删除、reshape、转置/查询/计数、含 `0×0` 系数及 shaped-empty 结果的稀疏方阵求解,以及保持 Matlab 结果 storage 的 finite-real sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放和 compatible-size sparse `.*` scalar/dense/sparse 乘法、实数/复数方阵 safe-integer 整数幂与多输出函数 | +| Matlab | `.m` | 脚本与局部函数、条件/循环/标量 `switch`、N 维逐元素 `~`/`&`/`|`、标量短路 `&&`/`||`、数组 condition truthiness、保持 shape 的 `all`/`any` 逻辑归约、实数与复数标量/数组、保持 shape 的 `0×0` 与零 extent 数组、静态 N 维及 local-function runtime-shape 隐式扩展、数组比较、静态及运行时 extent 的 `end`、保序/重复/空 numeric selector、线性/逐维 logical selector、vector/matrix/N 维扩容与单轴删除、普通/共轭转置、section、`reshape`、复数逐元素算术、二维实数/复数矩阵乘法、结构感知实数方阵求解、Hermitian 正定/稠密复数方阵求解、rank-aware 实数/复数矩形求解、R2024 全部 `sparse` 调用形式对静态 real/logical rank-2 输入的实现(全零构造仍返回 double)、logical triplet 重复坐标按 `any` 合并,以及保持类型的稀疏 scalar/linear/submatrix selection、indexed assignment、扩容、删除、reshape、转置/`full`/查询/计数、含 `0×0` 系数及 shaped-empty 结果的稀疏方阵求解,以及保持 Matlab 结果 storage 的 finite-real sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放和 compatible-size sparse `.*` scalar/dense/sparse 乘法、实数/复数方阵 safe-integer 整数幂与多输出函数 | | Python | `.py`、`.pyw` | 函数与参数、条件和循环、list/tuple、解包、比较链、多维数组、索引和切片 | | Fortran | `.f`、`.for`、`.ftn`、`.f77`、`.f90` 等 | free/fixed form、function/subroutine、`INTENT`/`OPTIONAL`、数组与 section、`SELECT CASE` | | TypeScript | `.ts`、`.mts`、`.cts` | 类型化标量和数组、函数、块作用域、条件、`while`、标准 C 风格 `for` | @@ -124,7 +124,7 @@ cmake --install build/release --prefix build/stage 在项目中查找当前精确版本: ```cmake -find_package(mpf 0.6.8 EXACT CONFIG REQUIRED COMPONENTS core cpp) +find_package(mpf 0.6.9 EXACT CONFIG REQUIRED COMPONENTS core cpp) target_link_libraries(my_application PRIVATE mpf::mpf) ``` @@ -152,7 +152,7 @@ int main() { } ``` -安装包提供 `core`、`javascript` 和 `cpp` component,以及 `mpf::core`、`mpf::backend-javascript`、`mpf::backend-cpp` 和统一入口 `mpf::mpf`。完整集成示例见 [`examples/embedding`](examples/embedding);配置时传入 `-DMPF_REQUIRED_VERSION=0.6.8`,以保持精确版本匹配。 +安装包提供 `core`、`javascript` 和 `cpp` component,以及 `mpf::core`、`mpf::backend-javascript`、`mpf::backend-cpp` 和统一入口 `mpf::mpf`。完整集成示例见 [`examples/embedding`](examples/embedding);配置时传入 `-DMPF_REQUIRED_VERSION=0.6.9`,以保持精确版本匹配。 MPF 0.x 有意只安装静态库。共享库需要先明确符号导出、allocator/所有权和版本协商契约;设置 `BUILD_SHARED_LIBS` 不会把当前内部 C++ ABI 意外暴露为受支持的动态库接口。 diff --git a/README.md b/README.md index 75330c2..e2a8923 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ A modern, high-performance multilingual transpilation framework. MPF converts su | Input language | Recognized extensions | Current capabilities | |---|---|---| -| Matlab | `.m` | Scripts and local functions, conditionals/loops/scalar `switch`, element-wise N-D `~`/`&`/`|`, scalar short-circuit `&&`/`||`, array condition truthiness, shape-aware `all`/`any` logical reductions, real and complex scalars/arrays, shape-preserving `0×0` and zero-extent arrays, static N-D and local-function runtime-shape implicit expansion, array comparisons, static and runtime-sized `end`, ordered/repeated/empty numeric and linear/per-dimension logical selectors, vector/matrix/N-D growth and single-axis deletion, ordinary/conjugate transpose, sections, `reshape`, complex element-wise arithmetic, two-dimensional real/complex matrix multiplication, structure-aware real square solves, Hermitian-positive-definite/dense complex square solves, rank-aware real/complex rectangular solves, all R2024 `sparse` call forms within the static real rank-2 CSC contract including zero extents, sparse scalar/linear/submatrix selection plus indexed assignment, growth, deletion, reshape, transpose/query/count, square solves including `0×0` coefficients and shape-preserving empty results, finite-real sparse×sparse/sparse×dense/dense×sparse matrix multiplication, bidirectional sparse/scalar scaling, and compatible-size sparse `.*` scalar/dense/sparse multiplication with Matlab-compatible canonical CSC results, safe-integer real/complex square matrix power, and multiple-output functions | +| Matlab | `.m` | Scripts and local functions, conditionals/loops/scalar `switch`, element-wise N-D `~`/`&`/`|`, scalar short-circuit `&&`/`||`, array condition truthiness, shape-aware `all`/`any` logical reductions, real and complex scalars/arrays, shape-preserving `0×0` and zero-extent arrays, static N-D and local-function runtime-shape implicit expansion, array comparisons, static and runtime-sized `end`, ordered/repeated/empty numeric and linear/per-dimension logical selectors, vector/matrix/N-D growth and single-axis deletion, ordinary/conjugate transpose, sections, `reshape`, complex element-wise arithmetic, two-dimensional real/complex matrix multiplication, structure-aware real square solves, Hermitian-positive-definite/dense complex square solves, rank-aware real/complex rectangular solves, all R2024 `sparse` call forms for statically shaped real or logical rank-2 inputs (zero-matrix construction remains double), logical duplicate triplets using `any`, type-preserving sparse scalar/linear/submatrix selection plus indexed assignment, growth, deletion, reshape, transpose/`full`/query/count, square solves including `0×0` coefficients and shape-preserving empty results, finite-real sparse×sparse/sparse×dense/dense×sparse matrix multiplication, bidirectional sparse/scalar scaling, and compatible-size sparse `.*` scalar/dense/sparse multiplication with Matlab-compatible canonical CSC results, safe-integer real/complex square matrix power, and multiple-output functions | | Python | `.py`, `.pyw` | Functions and parameters, conditionals and loops, lists/tuples, unpacking, comparison chains, multidimensional arrays, indexing, and slicing | | Fortran | `.f`, `.for`, `.ftn`, `.f77`, `.f90`, and others | Free/fixed form, functions/subroutines, `INTENT`/`OPTIONAL`, arrays and sections, and `SELECT CASE` | | TypeScript | `.ts`, `.mts`, `.cts` | Typed scalars and arrays, functions, block scope, conditionals, `while`, and standard C-style `for` loops | @@ -124,7 +124,7 @@ cmake --install build/release --prefix build/stage Find the exact current version in another project: ```cmake -find_package(mpf 0.6.8 EXACT CONFIG REQUIRED COMPONENTS core cpp) +find_package(mpf 0.6.9 EXACT CONFIG REQUIRED COMPONENTS core cpp) target_link_libraries(my_application PRIVATE mpf::mpf) ``` @@ -152,7 +152,7 @@ int main() { } ``` -The installed package provides the `core`, `javascript`, and `cpp` components; the `mpf::core`, `mpf::backend-javascript`, and `mpf::backend-cpp` targets; and the unified `mpf::mpf` entry point. See [`examples/embedding`](examples/embedding) for a complete integration example; configure it with `-DMPF_REQUIRED_VERSION=0.6.8` so the consumer keeps exact-version matching. +The installed package provides the `core`, `javascript`, and `cpp` components; the `mpf::core`, `mpf::backend-javascript`, and `mpf::backend-cpp` targets; and the unified `mpf::mpf` entry point. See [`examples/embedding`](examples/embedding) for a complete integration example; configure it with `-DMPF_REQUIRED_VERSION=0.6.9` so the consumer keeps exact-version matching. MPF 0.x installs static libraries deliberately. A supported shared-library ABI will require an explicit symbol-export, allocator/ownership, and version-negotiation contract; setting `BUILD_SHARED_LIBS` does not silently expose the current internal C++ ABI. diff --git a/TODO.md b/TODO.md index f546d8f..36b7270 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # MPF 持续建设路线图 -本路线图记录 **0.6.8 当前开发基线** 与后续交付目标的真实状态。历史交付细节见 +本路线图记录 **0.6.9 当前开发基线** 与后续交付目标的真实状态。历史交付细节见 [CHANGELOG-ZH.md](CHANGELOG-ZH.md),当前可依赖的语言子集见 [docs/LANGUAGE_SUPPORT.md](docs/LANGUAGE_SUPPORT.md)。目标版本号表示语法/语义覆盖上限,不表示已经完整兼容 Matlab 2024、Python 3.14、Fortran 2023 或 TypeScript 6;TypeScript 已有独立、可执行且包含 lexical block/canonical `for` 的子集,但完整 grammar 仍未完成。 @@ -14,12 +14,12 @@ | 输出目标 | 独立 JavaScript 与 `cpp` 后端;源码分别位于无重复文件名前缀的 `src/backends/javascript/`、`src/backends/cpp/`,`cpp` 当前生成严格 C++17 translation unit | | 前后端边界 | 四语言 parser session 直接构造并发布各自 arena AST artifact,不经过共享递归 syntax tree 或整树复制;生产驱动随后固定经过 HIR→MIR→共享优化→优化后 alias/effect→CFG memory-dependence→目标私有 semantic plan/LIR→emitter,两个目标不读取彼此产物 | | 扩展架构 | frontend descriptor API v6、backend descriptor API v6;仅接受 canonical 语言/目标名称,不保留旧名称 alias;parser session/feature/resource contract、configuration/runtime supply-chain manifest、AST verifier、TargetProfile、稠密 legalization、opaque artifact 和前后端 conformance harness 已接入 | -| IR 架构 | 四种语言使用编译期互不兼容的 PMR arena AST,并原子 lowering 到窄 HIR v2 与 revision-checked 稠密 semantic side table;名称、控制流、alias/effect 和 memory-dependence 分别由独立表持有。MIR v27 使用强类型稠密 expression/statement/instruction arena、显式 CFG、resident instruction 与 revision-bound attributes;Semantic v21→MIR v27→双目标 LIR v34 逐层验证 numeric class/complexity、dense/CSC array storage、matrix numeric domain/solve/condition/factorization/structure/storage、sparse construction/index/mutation/reshape/product/scale 与独立 `SparseElementwisePlan`、shape/broadcast/reduction/index/mutation、storage region、call ownership/writeback、目标 runtime shape 调用 ABI 和 source segment plan。JavaScript LIR 与 `cpp` LIR 各自完成 representation/runtime/module 规划,emitter 仅序列化,任一目标都不读取另一目标产物。 | +| IR 架构 | 四种语言使用编译期互不兼容的 PMR arena AST,并原子 lowering 到窄 HIR v2 与 revision-checked 稠密 semantic side table;名称、控制流、alias/effect 和 memory-dependence 分别由独立表持有。MIR v28 使用强类型稠密 expression/statement/instruction arena、显式 CFG、resident instruction 与 revision-bound attributes;Semantic v22→MIR v28→双目标 LIR v35 逐层验证 numeric class/complexity、dense/CSC array storage、matrix numeric domain/solve/condition/factorization/structure/storage、sparse construction 的值域/重复项策略、index/mutation/reshape/product/scale 与独立 `SparseElementwisePlan`、shape/broadcast/reduction/index/mutation、storage region、call ownership/writeback、目标 runtime shape/integer 调用 ABI 和 source segment plan。JavaScript LIR 与 `cpp` LIR 各自完成 representation/runtime/module 规划,emitter 仅序列化,任一目标都不读取另一目标产物。 | | Python 最新能力 | relational/equality/identity/membership 比较链、右结合条件表达式、短路/惰性/单次求值;list/tuple 种类相等规则、singleton/reference identity、string/list/tuple membership;基础参数关联和递归固定序列解包 | | 跨语言标量除法 | HIR v2 profile 独立保存 quotient 与 zero-denominator policy;Python `/`/`//` 在 JavaScript/C++17 中 checked exception,Matlab/TypeScript 保持 IEEE-754;C++ 统一经过参数化 runtime helper,消除 MSVC 对惰性字面量零除的编译期拒绝;Fortran 当前保持 target-native | -| Matlab 最新能力 | `~`/`&`/`|` 的 compatible-size N 维逐元素逻辑、`&&`/`||` 标量短路、condition 全元素非零/非空 truthiness 与 condition-context scalar `&`/`|` 短路;`all`/`any` 支持默认首个 non-singleton 维、常量 `dim`/`vecdim`、`'all'`、N 维 shape 与空归约 identity;`2i`/`3j` 及可遮蔽 builtin `i`/`j`、complex scalar 算术/幂/一元正负、标量 `complex`/`conj`/`real`/`imag`/`abs`、complex 数组 compatible-size 逐元素运算、索引/写入/reshape,以及 `'` 共轭转置与 `.'` 普通转置;跨 local-function 的未知 real/complex 参数由动态 numeric ABI 分派;scalar numeric/logical/character `switch/case/otherwise`;规范 `0×0` double empty、一般静态零 extent reshape/transpose/broadcast/section/growth;R2024 全部 `sparse` 调用形式在静态 real rank-2 contract(包含零 extent)内的 canonical CSC 构造、scalar expansion、duplicate accumulation、显式 sparse transpose、scalar/linear/submatrix indexing、indexed assignment、列主序 `reshape`(size vector、维度列表、单个 `[]` 推断与 N 维请求折叠为二维 sparse 结果)、重复下标 last-write-wins、零值删除、静态扩容/null deletion、`full`/`issparse`/`nnz`、含 `0×0` 系数与 shaped-empty 两侧操作数的实数方阵稀疏系数左右除,以及 finite-real rank-2 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放和 sparse/dense/scalar compatible-size `.*` 并保持显式结果 shape 与 canonical CSC storage;二维矩阵乘法、静态稠密实数 diagonal/upper/lower/pivoted-tridiagonal/symmetric-positive-definite/dense 结构感知方阵、复数 Hermitian Cholesky/dense LU 方阵及 real/complex rank-aware 超定/欠定 solve、safe-integer 方阵 power、静态 N 维及 local-function runtime rank/extent 的 compatible-size 算术/关系比较、静态及运行时 extent 的逐维/线性 `end`、保序/重复/空 numeric selector、线性/逐维 logical selector,以及 vector/matrix/N 维多轴自动扩容与单轴索引删除进入双目标专属 LIR/runtime | +| Matlab 最新能力 | `~`/`&`/`|` 的 compatible-size N 维逐元素逻辑、`&&`/`||` 标量短路、condition 全元素非零/非空 truthiness 与 condition-context scalar `&`/`|` 短路;`all`/`any` 支持默认首个 non-singleton 维、常量 `dim`/`vecdim`、`'all'`、N 维 shape 与空归约 identity;`2i`/`3j` 及可遮蔽 builtin `i`/`j`、complex scalar 算术/幂/一元正负、标量 `complex`/`conj`/`real`/`imag`/`abs`、complex 数组 compatible-size 逐元素运算、索引/写入/reshape,以及 `'` 共轭转置与 `.'` 普通转置;跨 local-function 的未知 real/complex 参数由动态 numeric ABI 分派;scalar numeric/logical/character `switch/case/otherwise`;规范 `0×0` double empty、一般静态零 extent reshape/transpose/broadcast/section/growth;R2024 全部 `sparse` 调用形式对静态 real/logical rank-2 值输入(包含零 extent)的 canonical CSC 构造、scalar expansion、numeric duplicate sum 与 logical duplicate `any`、显式 sparse transpose、scalar/linear/submatrix indexing、indexed assignment、保持 real/logical class 的列主序 `reshape`(size vector、维度列表、单个 `[]` 推断与 N 维请求折叠为二维 sparse 结果)、重复下标 last-write-wins、零值删除、静态扩容/null deletion、`full`/`issparse`/`nnz`、含 `0×0` 系数与 shaped-empty 两侧操作数的实数方阵稀疏系数左右除,以及 finite-real rank-2 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放和 sparse/dense/scalar compatible-size `.*` 并保持显式结果 shape 与 canonical CSC storage;二维矩阵乘法、静态稠密实数 diagonal/upper/lower/pivoted-tridiagonal/symmetric-positive-definite/dense 结构感知方阵、复数 Hermitian Cholesky/dense LU 方阵及 real/complex rank-aware 超定/欠定 solve、safe-integer 方阵 power、静态 N 维及 local-function runtime rank/extent 的 compatible-size 算术/关系比较、静态及运行时 extent 的逐维/线性 `end`、保序/重复/空 numeric selector、线性/逐维 logical selector,以及 vector/matrix/N 维多轴自动扩容与单轴索引删除进入双目标专属 LIR/runtime | | Fortran 最新能力 | integer/character/logical `SELECT CASE`、范围/default、重叠检查和任意分支确定赋值合流;已知静态 shape 下可证明不相交的同根连续、步长与 N 维矩形 writable section actual | -| 工程门禁 | 268 项内部测试;98 个差分 case、241 条工具完整环境执行路径;Debug/Release/RelWithDebInfo 127 项 CTest;四语言 fuzz smoke、可选 libFuzzer、18 项生成 runtime 拒绝测试、发布脚本正/负契约、31 项独立版本化通用及 Matlab 专项性能阈值、逐 pass/优化/内存依赖统计报告;生产代码行覆盖率硬门槛为 85%;Release 在标签 SHA 上复用七类 required workflow,门禁后才允许三平台候选测试/安装/消费/归档、来源证明和公开资产回验 | +| 工程门禁 | 270 项内部测试;99 个差分 case、243 条工具完整环境执行路径;Debug/Release/RelWithDebInfo 129 项 CTest;四语言 fuzz smoke、可选 libFuzzer、19 项生成 runtime 拒绝测试、发布脚本正/负契约、32 项独立版本化通用及 Matlab 专项性能阈值、逐 pass/优化/内存依赖统计报告;生产代码行覆盖率硬门槛为 85%;Release 在标签 SHA 上复用七类 required workflow,门禁后才允许三平台候选测试/安装/消费/归档、来源证明和公开资产回验 | | 发布状态 | 0.x 开发快照;包消费要求精确当前版本,不提供旧 MPF API/ABI/schema/CLI/CMake 兼容承诺或迁移 shim | ## 本轮商业级收尾验收(完成) @@ -64,8 +64,9 @@ - [x] Matlab P0 sparse scalar-product 纵切面:`MatrixStoragePolicy::sparse_csc_scale` 在 Semantic v20、MIR v26 和双目标 LIR v32 固化 scalar 方向、CSC 结果 storage 与 shape;finite real/logical 非零因子由 JavaScript/C++ 独立 O(nnz) kernel 执行,零因子以 O(columns) 产生同 shape 空 CSC,并覆盖 source map、负向/跨层/LIR 损坏、差分、生成 nonfinite/overflow runtime 拒错、fuzz、架构和第三十项独立性能预算 - [x] Matlab P0 sparse element-wise 纵切面:独立 `SparseElementwisePlan` 在 Semantic v21、MIR v27 和双目标 LIR v33 固化 `.*` identity、五种 scalar/dense/CSC operand direction、静态 compatible-size 逐轴广播、canonical CSC 结果 storage 与 finite-real contract;JavaScript/C++ 独立 nonzero-driven kernel 不复用 matrix-product plan,并覆盖 source map、负向/跨层/LIR 损坏、双目标执行差分、生成 nonfinite/overflow/计划污染拒错、fuzz、架构和第三十一项独立性能预算 - [x] Matlab P0 静态零 extent sparse 收尾:constructor/transpose/index/mutation/reshape/matrix-product/scalar-product/element-wise 与方阵左右除均保留显式 shape;双目标 LIR v34 以可验证、可 dump 的 `runtime_shape_arguments` 固化 runtime 调用 ABI,JavaScript/C++ renderer 不读取 semantic storage/shape policy;差分 corpus、双目标生成执行、source map、计划篡改拒错、fuzz、架构与既有 sparse-solve 性能 workload 完成 +- [x] Matlab P0 logical sparse storage 纵切面:`SparseValueDomain` 与 `SparseDuplicatePolicy` 在 Semantic v22、MIR v28 和双目标 LIR v35 固化 finite-real/logical CSC 与 numeric-sum/logical-any 重复项语义;`sparse(A)`、logical triplet、转置、scalar/linear/submatrix selection、indexed mutation/growth/deletion、reshape、`full`/`issparse`/`nnz` 均保持 logical class,JavaScript 使用带 `valueDomain` 的 tagged CSC,`cpp` 使用 `sparse_matrix`;目标 LIR 独立选择 runtime integer ABI/helper,renderer 只序列化,并覆盖 source map、逐层/LIR 损坏、双目标差分、生成 shape-plan 篡改拒错、fuzz、架构与第 32 项性能预算 - [x] 按 Matlab null-assignment 规则固定删除边界:只允许 vector 线性删除,或恰好一个非 colon 维度的整 slice 删除;非 vector 线性删除和多个非 colon selector 是源语言非法语义,不再列为未来能力 -- [ ] 继续 Matlab P0:sparse logical/power/rectangular/complex 与动态 source-shape sparse 语义,病态方阵特定解选择的精确 Matlab 对齐、其余 numeric class,以及可跨函数传播不可结构恢复 shape 的统一动态 NDArray ABI;R2024 full symmetric-indefinite 已按当前 dense LU 行为处理,不把已移除的 LDL 路径列为兼容前提 +- [ ] 继续 Matlab P0:sparse logical 算术/逐元素逻辑/归约、sparse power/rectangular/complex 与动态 source-shape sparse 语义,病态方阵特定解选择的精确 Matlab 对齐、其余 numeric class,以及可跨函数传播不可结构恢复 shape 的统一动态 NDArray ABI;R2024 full symmetric-indefinite 已按当前 dense LU 行为处理,不把已移除的 LDL 路径列为兼容前提 - [ ] 按 Python/Fortran/TypeScript 官方 grammar 选择下一批可独立验收的纵切面 - [ ] 继续完成跨语言动态 shape 数据流、类型化 NDArray/typed-array ownership、跨一般 view/pointer 的 region/alias 证明 - [ ] 在已交付的区域化 memory-dependence contract 上建立 memory version、memory phi、rename/def-use 的完整 MemorySSA;以负向 verifier、差分、fuzz 和性能门禁后再启用 region-aware DCE/store forwarding @@ -189,8 +190,8 @@ - [ ] 元胞数组、struct、string、table、datetime 等核心类型 - [x] exact-zero diagonal/upper/lower 与 dense fallback 的首批结构感知 solver dispatch,包含左右除和结构对应条件 warning - [x] full-real tridiagonal 相邻行部分主元 LU、正/转置求解、条件估计,以及 exact-symmetric positive-definite Cholesky;对称非正定候选回退 dense LU -- [x] 静态 real rank-2 CSC(包含零 extent):R2024 全部 `sparse` 调用形式、dense/CSC conversion/query/count、直接 canonical triplet construction、scalar expansion、duplicate accumulation/cancellation、普通/共轭 sparse transpose、scalar/linear/submatrix indexing、indexed assignment/growth/deletion、size-vector/维度列表/单 `[]` 推断的列主序 reshape、含 `0×0` 系数和 shaped-empty operand/result 的稀疏实数方阵左右除,以及 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放、compatible-size sparse/dense/scalar `.*` 和 storage-preserving result 子集 -- [ ] sparse logical/power/rectangular/complex 与动态 source-shape 语义,病态方阵特定解选择的精确 Matlab 对齐、非整数矩阵幂、完整 numeric class,以及一般 NDArray 值语义 +- [x] 静态 real/logical rank-2 CSC storage(包含零 extent):R2024 全部 `sparse` 调用形式对相应值输入的 dense/CSC conversion/query/count、直接 canonical triplet construction、scalar expansion、numeric duplicate sum/cancellation、logical duplicate `any`、保持 class 的普通/共轭 sparse transpose、scalar/linear/submatrix indexing、indexed assignment/growth/deletion、size-vector/维度列表/单 `[]` 推断的列主序 reshape、含 `0×0` 系数和 shaped-empty operand/result 的稀疏实数方阵左右除,以及 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放、compatible-size sparse/dense/scalar `.*` 和 storage-preserving result 子集 +- [ ] sparse logical 算术/逐元素逻辑/归约、power/rectangular/complex 与动态 source-shape 语义,病态方阵特定解选择的精确 Matlab 对齐、非整数矩阵幂、完整 numeric class,以及一般 NDArray 值语义 - [ ] nested/anonymous function 和完整 function workspace/closure 语义 - [ ] `classdef`、properties、methods、events 与 handle/value 对象模型 - [ ] 核心函数与工具箱 API 的分层映射、许可证和版本策略 diff --git a/cmake/verify_release_version.cmake b/cmake/verify_release_version.cmake index ab644b5..77a5b9d 100644 --- a/cmake/verify_release_version.cmake +++ b/cmake/verify_release_version.cmake @@ -40,6 +40,24 @@ function(mpf_verify_changelog changelog_path changelog_name result_variable) "release ${project_version} in ${changelog_name} must contain 8-20 entries; " "found ${release_entry_count}") endif() + + set(forbidden_release_note_phrases + "The validation baseline now contains" + "Production source coverage" + "production line coverage" + "The release gate now includes" + "验证基线现包含" + "生产源码覆盖率" + "生产代码行覆盖率" + "发布门禁现包含") + foreach(forbidden_phrase IN LISTS forbidden_release_note_phrases) + string(FIND "${release_section}" "${forbidden_phrase}" forbidden_position) + if(NOT forbidden_position EQUAL -1) + message(FATAL_ERROR + "release ${project_version} in ${changelog_name} contains internal validation " + "metadata '${forbidden_phrase}'; user-facing changelogs must describe product changes") + endif() + endforeach() set(${result_variable} "${release_entry_count}" PARENT_SCOPE) endfunction() diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e17713e..811144a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,6 @@ # MPF 架构 -本文描述 0.6.8 当前源码树的实际架构。CMake 同时固定 C17/C++17 标准基线,但当前生产实现和公共 API 使用 C++17;`cpp` 是 C++ 输出目标的代码身份,C++17 是当前生成标准。项目仍处于 0.x 开发期,只维护这里描述的精确当前 contract,不提供旧 MPF 兼容层。最终职责、性能模型和后续迁移验收条件见 [商业级编译器管线方案](COMPILER_PIPELINE.md)。 +本文描述 0.6.9 当前源码树的实际架构。CMake 同时固定 C17/C++17 标准基线,但当前生产实现和公共 API 使用 C++17;`cpp` 是 C++ 输出目标的代码身份,C++17 是当前生成标准。项目仍处于 0.x 开发期,只维护这里描述的精确当前 contract,不提供旧 MPF 兼容层。最终职责、性能模型和后续迁移验收条件见 [商业级编译器管线方案](COMPILER_PIPELINE.md)。 ## 当前状态边界 @@ -8,40 +8,40 @@ - 四个 statement parser 通过 `FrontendAstBuilder` 直接产生编译期互不兼容的语言 AST artifact;递归表达式解析后立即驻留,statement body/root 只保存稠密 `AstNodeId`,错误恢复也只发布可达节点。顶层 arena 容器使用 parser session PMR resource,不存在跨语言递归 syntax tree 或 parse 后整树复制;随后显式运行 AST verifier,AST→HIR visitor 原子产出 HIR v2 窄结构与按 `HirNodeId` 稠密、绑定 revision 的 `SemanticTable` seed; - Analyzer 和 HIR pass 只处理 HIR 与显式 side-table 输入;独立的只读 name/flow pass 分别生成 revision-bound 稠密 `NameTable` 与 `FlowTable`,负责 lexical scope/symbol/builtin、reachability/termination 和不可达诊断。`NameTable` 以 profile 驱动的 `ScopeModel` 选择 function 或 lexical-block 规则,并通过 `NameScopeEdges` 把 function、statement、body、alternative scope 绑定到 HIR owner/kind;声明只落入当前 scope,赋值/引用解析最近祖先 binding,verifier 拒绝缺失、错误 parent/kind 和非稠密 edge。Analyzer 在运行任何 pass 前验证 frontend seed,以 `ScopeId`/`SymbolId` 稠密状态消费两表并原位完善 `SemanticTable`,branch/loop 内隔离局部状态而只合并外层 binding。静态已知 shape 的 identifier、element、N 维 rectangular section 与列主序 linear section 同时规范化为 `StorageRegion` fact;动态 bound/extent 保持 unknown。HIR 节点不保存 type、shape、binding、call association、storage region 或 assignment-pattern 镜像;HIR→MIR 只从 semantic side table 读取这些 facts,并从 name table 固化 storage/function 的 `SymbolId` identity; -- MIR lowering 以 `MirExpressionId`/`MirStatementId` 建立带零号哨兵的稠密 expression/operation arena;child、body/alternative 和 roots 只保存强类型 ID,每个节点绑定 resident instruction,递归 HIR 兼容树不再驻留。MIR v27 的 revision-bound `OperationAttributeTable` 分别按 expression、statement 和 `InstructionId` 稠密保存 spelling、comparison/binding/intrinsic、Matlab broadcast/reduction/matrix-operation/sparse-construction/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise/solve/condition-policy/factorization-policy/structure-policy plan、逐下标 selector/extent plan、调用策略、规范化 storage region、强类型 assignment facts 以及零到多个 `MemoryAccess`;每条访问显式保存 storage、最终 root、region 和 read/write/read-write mode,结构 `Instruction` 不重新耦合这些分析事实。if/loop/loop-else/`break`/`continue`/`SELECT CASE` 具有真实 CFG;conditional、逻辑短路和 comparison chain 进一步产生分支专属 block、`truthiness`/`compare`、typed block argument 和 edge actual。TypeScript canonical `for` 另有 preheader、condition、body、update、exit block,initializer/update 是显式 `store`,continue edge 进入 update,再由回边携带 storage actual。变量读取、声明/赋值和 section writable call 产生 `load`、`allocate`、`store`/`store_indexed`、`copy`/`writeback`,相应 read/write 区域同时进入指令属性。shape stride、storage view/lifetime/intent/optional 与 tuple/function/reference type/shape 签名均使用强类型 ID;全局 storage 以 NameTable 派生的 `SymbolId` 跨函数共享身份; +- MIR lowering 以 `MirExpressionId`/`MirStatementId` 建立带零号哨兵的稠密 expression/operation arena;child、body/alternative 和 roots 只保存强类型 ID,每个节点绑定 resident instruction,递归 HIR 兼容树不再驻留。MIR v28 的 revision-bound `OperationAttributeTable` 分别按 expression、statement 和 `InstructionId` 稠密保存 spelling、comparison/binding/intrinsic、Matlab broadcast/reduction/matrix-operation/sparse-construction/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise/solve/condition-policy/factorization-policy/structure-policy plan、逐下标 selector/extent plan、调用策略、规范化 storage region、强类型 assignment facts 以及零到多个 `MemoryAccess`;每条访问显式保存 storage、最终 root、region 和 read/write/read-write mode,结构 `Instruction` 不重新耦合这些分析事实。if/loop/loop-else/`break`/`continue`/`SELECT CASE` 具有真实 CFG;conditional、逻辑短路和 comparison chain 进一步产生分支专属 block、`truthiness`/`compare`、typed block argument 和 edge actual。TypeScript canonical `for` 另有 preheader、condition、body、update、exit block,initializer/update 是显式 `store`,continue edge 进入 update,再由回边携带 storage actual。变量读取、声明/赋值和 section writable call 产生 `load`、`allocate`、`store`/`store_indexed`、`copy`/`writeback`,相应 read/write 区域同时进入指令属性。shape stride、storage view/lifetime/intent/optional 与 tuple/function/reference type/shape 签名均使用强类型 ID;全局 storage 以 NameTable 派生的 `SymbolId` 跨函数共享身份; - `BinaryOperator` 是普通与 Matlab 逐元素二元运算的规范身份,`UnaryOperator` 独立保存共轭/非共轭转置;两者随语言 AST、HIR、MIR `OperationAttributeTable` 和两个目标 LIR 传递,源 spelling 只用于诊断/调试。Matlab compatible-size 运算由 `BroadcastPlan` 保存 `static_extents` 或 `runtime_operands` shape source;静态 rank 继续携带两侧/结果 shape 与逐轴 match/expand/runtime mode,未知 rank 则以显式 runtime source 和空轴清单表示,不能伪装成静态空 shape。`ReductionPlan` 为 `all`/`any` 保存 operation、默认首个非 singleton/显式维度/全维 axis policy、static-extents/runtime-operand shape source、归约轴、输入/输出 shape 和 scalar-result identity;显式 `dim`/`vecdim` 在 Analyzer 固定,未知 rank 仅允许不依赖 rank 的全维归约。`MatrixOperationPlan` 保存 multiply/left-divide/right-divide/integer-power、square/overdetermined/underdetermined solve kind、`square_continue_with_warning`/`basic_solution_with_warning` 数值条件策略、real/complex `MatrixNumericDomain`、rectangular `rank_revealing_column_pivoted_qr` 或 sparse row-pivoted LU 分解策略、`classify_real_square`/`classify_complex_square`/`classify_sparse_real_square` 结构策略、dense/CSC coefficient/CSC-product storage policy、两侧/结果 storage 及输入输出 shape;`SparseElementwisePlan` 独立保存 `.*` operation、preserve-sparse policy、左右 storage/shape、compatible-size broadcast axis 与结果 shape;`IndexSelectorKind` 为每个下标分别保存 scalar/slice/numeric/logical/empty 身份,`IndexExtentSource` 则明确区分无运行时长度、当前轴长度和列主序元素总数。Matlab `[]` 在 semantic side table 中固定为 real、column-major、`0×0` sequence;目标 `ArrayLiteralPlan` 区分 direct 与 shaped-empty,防止 renderer 从空嵌套结构猜测 rank。HIR/MIR/目标 verifier 逐层重算并拒绝缺失、错误 arity/source、错误 reduction/selector/extent/solve/condition/factorization/structure/storage policy、矛盾 shape 或错误 expression kind。Analyzer、MIR constant folding 和 target representation 不从 renderer 中反向猜测操作。两个目标分别实现二维实数/复数矩阵乘法、静态 real rank-2 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、compatible-size CSC `.*` scalar/dense/CSC、静态稠密实数方阵 diagonal/upper/lower/pivoted-tridiagonal/symmetric-positive-definite/dense 结构感知求解、静态 real rank-2 CSC 方阵求解、稠密复数方阵 Hermitian-positive-definite Cholesky/dense LU 求解、矩形实数/复数 rank-aware CPQR 基本最小二乘解、实数/复数 safe-integer 方阵幂、静态 N 维及 local-function runtime rank/extent broadcast、数组比较、`all`/`any` 逻辑归约、vector/rank-2 转置、静态及动态 `end`、保序/重复/空 numeric selector、线性/逐维 logical selector及上述 static real rank-2 CSC 读写索引;runtime broadcast 和 reduction 对操作数只求值一次,验证矩形性、shape、axis 和 singleton 兼容性并保持标量结果与空归约 identity。矩形数值秩亏继续返回 pivoted basic solution 并稳定警告;方阵通过所选结构 kernel 的正/转置求解迭代估计 1-范数 `rcond`,精确奇异与近奇异分别警告后继续;未实现矩阵语义仍在 lowering/runtime 边界失败关闭; -- `NumericType` 与 coarse `ValueType`、shape 和 storage 独立:`NumericClass` 当前区分 logical、signed-integer 与 binary64,`NumericComplexity` 区分 real 与 complex;`none`、`unknown` 与非法组合同样有明确身份。语言 AST expression/assignment pattern、HIR `SemanticTable`、MIR `TypeData`、JavaScript LIR 和 `cpp` LIR 都为 scalar、element、tuple、parameter、return 和 multi-target 保存该事实,Semantic v21、MIR v27 和 LIR v34 verifier 会拒绝丢失、陈旧或 logical/integer-complex 等矛盾组合。Matlab 无类型 local-function 参数保留 unknown complexity,不能在 MIR interning 时按 coarse real/integer 擅自 canonicalize;目标 representation 因此选择动态 numeric call。JavaScript 以私有 `Symbol` tag 的 `{re, im}` object 实现 complex ABI,`cpp` 使用 `std::complex`,两者分别拥有 arithmetic/broadcast/transpose runtime 和 feature pruning,生成任一目标不需要先生成或读取另一目标; +- `NumericType` 与 coarse `ValueType`、shape 和 storage 独立:`NumericClass` 当前区分 logical、signed-integer 与 binary64,`NumericComplexity` 区分 real 与 complex;`none`、`unknown` 与非法组合同样有明确身份。语言 AST expression/assignment pattern、HIR `SemanticTable`、MIR `TypeData`、JavaScript LIR 和 `cpp` LIR 都为 scalar、element、tuple、parameter、return 和 multi-target 保存该事实,Semantic v22、MIR v28 和 LIR v35 verifier 会拒绝丢失、陈旧或 logical/integer-complex 等矛盾组合。Matlab 无类型 local-function 参数保留 unknown complexity,不能在 MIR interning 时按 coarse real/integer 擅自 canonicalize;目标 representation 因此选择动态 numeric call。JavaScript 以私有 `Symbol` tag 的 `{re, im}` object 实现 complex ABI,`cpp` 使用 `std::complex`,两者分别拥有 arithmetic/broadcast/transpose runtime 和 feature pruning,生成任一目标不需要先生成或读取另一目标; - `ArrayStorageFormat` 与 `ValueType`/shape/`StorageRegion` 分离:`none` 表示非数组槽位,`unknown` 表示运行时未决表示,`dense` 与 `sparse_csc` 是已解析的物理表示。语言 AST expression/assignment pattern、symbol state、HIR 全量 side table、MIR interned `TypeData`、statement/tuple/function ABI 和两个目标 LIR 都传播该事实;分支合流只在相同格式上保持已知表示,否则保守变为 unknown。`MatrixStoragePolicy::sparse_csc_coefficient` 只授权静态 real square coefficient 的左右除,结果格式对左除跟随 RHS、对右除跟随 LHS;`sparse_csc_multiply` 则授权至少一侧为 CSC 的静态 real rank-2 乘法,并固定 sparse×sparse→CSC、混合乘法→dense;`sparse_csc_scale` 则授权一侧为 CSC、另一侧为 finite real/logical scalar 的双向矩阵乘法,并固定结果为 canonical CSC。`SparseElementwisePlan` 则在矩阵计划之外固定 `.*` 的左右 storage/shape、broadcast axis 与 preserve-sparse 结果。C++ representation planner 将 CSC 声明直接映射为 `mpf_runtime::sparse_matrix`,不先物化嵌套 `std::vector`;JavaScript 使用私有 `Symbol` tag 的 canonical CSC object。Semantic/MIR/LIR verifier 交叉检查值类型、子表达式格式、matrix policy 和结果格式,renderer 只序列化已选 helper; - 每个 user-call argument 是单一 region contract,保存 type、storage/root、intent、transfer、view、lifetime、writability 和 `StorageRegion`,transfer 区分 value/borrow/copy/optional-forward/omitted;copy-out 与 copy-in/out 在 call 前后形成不同 operand arity 的 temporary/writeback 指令。region 将 selector 规范化为各维零基 `first:stride:count`,同根 rectangular 区域只要任一维不相交即可证明整体 `no_alias`,列主序单 selector 使用 linearized 区域;未知、跨 kind/shape、尚未组合的一般嵌套 view 或超出有界证明能力的关系保持保守。alias relation 和 instruction/function/call effect 不内嵌结构 MIR,而由 revision-bound、可缓存的 `AliasEffectTable` v3 计算;显式 load/store/copy/writeback 从指令属性贡献区域化 read/write/allocate,跨函数 fixed point 再把 callee 参数访问按每个 actual region 实例化到 call instruction。访问级 `alias_between` 和 `memory_accesses_conflict` 是公共区域查询。独立 `MemoryDependenceTable` v1 随后在每个函数 CFG 上做 fixed point,以 `MemoryAccessSite`/`MemoryDependenceId` 建立 flow/anti/output 边;must-alias write 收敛 frontier,可证明 disjoint 的 region 不建边,unknown access 形成显式 barrier,CFG 回边传播 loop-carried provenance。该表按 `InstructionId` 稠密保存 incoming/outgoing adjacency,并与 alias/effect 一样绑定最终 MIR revision、由 `AnalysisManager` 缓存。MIR verifier 交叉检查 HIR/MIR expression fact、instruction access 与 call argument region,并检查 attribute revision/density、storage root/mode/mutability、operation ownership/可达性、lazy merge、CFG/dominance、type/shape/storage、函数签名和 call transfer/lifetime;alias/effect verifier 独立重算区域访问和跨函数 fixed point,memory-dependence verifier 再独立重算 CFG fixed point 并检查强类型 site、稠密 ID、adjacency、hazard kind、alias relation、barrier 与循环标记; -- 共享 MIR 默认优化在两个后端分叉前按 shape canonicalization、相同 edge-actual block-argument propagation、共同精确整数/布尔 constant folding + dead-pure elimination、保守 CFG cleanup 的固定顺序运行。每个 pass 都提升并同步 MIR/attribute revision、失效未保留分析、记录 instrumentation 并重新执行结构 verifier。整数折叠只覆盖 `int64` 与 ECMAScript safe-integer 的共同精确域;retired expression 使用 MIR v27 tombstone contract 保持 ID 稳定并清空 region、broadcast、matrix、sparse-construction、sparse-index、sparse-mutation、sparse-reshape、sparse-elementwise、selector 与 extent plan,instruction 与其 `InstructionAttributes` 则作为一个稠密单元紧凑重映射,block 同步重映射。alias/effect 与 memory dependence 只针对最终优化 revision 依次计算和验证,binding/capability 和两个后端看不到未优化或彼此不同的 MIR; -- JavaScript 与 `cpp` 通过 backend descriptor API v6 显式接收并验证 MIR 与 alias/effect facts,再分别执行 TargetProfile、逐 opcode legalization、capability、私有 semantic plan、独立 LIR/pass/verifier;两个目标 LIR v34 为 expression/statement/parameter/return/multi-target/declaration 固化 `SymbolId`、numeric class/complexity 与 array storage,名称 inventory 验证 symbol-spelling identity 与目标保留字碰撞。Program/function/statement/body/alternative `ScopePlan` 保存 lowering 后的 lexical declaration,renderer 不再按 spelling 或递归结构推导绑定;LIR 同时保存 ABI、CSR 临时资源、module/translation-unit 拓扑、source export、Matlab array-literal/broadcast/reduction shape source、sparse-construction kind/result-shape/triplet-count/reserve、sparse-index kind/input-result-shape/storage、sparse-mutation kind/replacement/duplicate/zero-policy 与 source/selection/replacement/result shape/storage、sparse-reshape kind/dimension-form/inference/shape/storage、sparse-elementwise operation/storage/broadcast/shape、matrix-operation kind/solve/condition-policy/factorization-policy/structure-policy/storage-policy/shape、逐下标 selector/extent identity、显式 `runtime_shape_arguments` 调用 ABI 与目标 expression/statement representation。representation planner 独立重算 ABI,verifier 比较完整 shape 实参序列,deterministic dump 公开该计划;renderer 只遍历并序列化这些目标实参,不读取 semantic matrix/sparse policy。强类型 `ComparisonForm` 固化 equality/ordering/identity/membership,单一 `CallArgumentPlan` 同时固化传递 ownership 与 writeback,`EvaluationForm`/call value/outcome 固化 comparison/lazy/writable-call 的 IIFE/lambda/thunk 和结果保存;按 `LirNodeId` 稠密的 `SourceSegmentPlan` 固化每个目标节点的 source/origin; +- 共享 MIR 默认优化在两个后端分叉前按 shape canonicalization、相同 edge-actual block-argument propagation、共同精确整数/布尔 constant folding + dead-pure elimination、保守 CFG cleanup 的固定顺序运行。每个 pass 都提升并同步 MIR/attribute revision、失效未保留分析、记录 instrumentation 并重新执行结构 verifier。整数折叠只覆盖 `int64` 与 ECMAScript safe-integer 的共同精确域;retired expression 使用 MIR v28 tombstone contract 保持 ID 稳定并清空 region、broadcast、matrix、sparse-construction、sparse-index、sparse-mutation、sparse-reshape、sparse-elementwise、selector 与 extent plan,instruction 与其 `InstructionAttributes` 则作为一个稠密单元紧凑重映射,block 同步重映射。alias/effect 与 memory dependence 只针对最终优化 revision 依次计算和验证,binding/capability 和两个后端看不到未优化或彼此不同的 MIR; +- JavaScript 与 `cpp` 通过 backend descriptor API v6 显式接收并验证 MIR 与 alias/effect facts,再分别执行 TargetProfile、逐 opcode legalization、capability、私有 semantic plan、独立 LIR/pass/verifier;两个目标 LIR v35 为 expression/statement/parameter/return/multi-target/declaration 固化 `SymbolId`、numeric class/complexity 与 array storage,名称 inventory 验证 symbol-spelling identity 与目标保留字碰撞。Program/function/statement/body/alternative `ScopePlan` 保存 lowering 后的 lexical declaration,renderer 不再按 spelling 或递归结构推导绑定;LIR 同时保存 ABI、CSR 临时资源、module/translation-unit 拓扑、source export、Matlab array-literal/broadcast/reduction shape source、sparse-construction kind/value-domain/duplicate-policy/result-shape/triplet-count/reserve、sparse-index kind/input-result-shape/storage、sparse-mutation kind/replacement/duplicate/zero-policy 与 source/selection/replacement/result shape/storage、sparse-reshape kind/dimension-form/inference/shape/storage、sparse-elementwise operation/storage/broadcast/shape、matrix-operation kind/solve/condition-policy/factorization-policy/structure-policy/storage-policy/shape、逐下标 selector/extent identity、显式 `runtime_shape_arguments`/`runtime_integer_arguments` 调用 ABI 与目标 expression/statement representation。representation planner 独立重算 ABI,verifier 比较完整目标实参序列,deterministic dump 公开该计划;renderer 只遍历并序列化这些目标实参,不读取 semantic matrix/sparse policy。强类型 `ComparisonForm` 固化 equality/ordering/identity/membership,单一 `CallArgumentPlan` 同时固化传递 ownership 与 writeback,`EvaluationForm`/call value/outcome 固化 comparison/lazy/writable-call 的 IIFE/lambda/thunk 和结果保存;按 `LirNodeId` 稠密的 `SourceSegmentPlan` 固化每个目标节点的 source/origin; - renderer 不再根据 section AST 猜测 copy/writeback、扫描 call argument 选择 wrapper、读取源 parameter intent、递归扫描声明或动态分配临时名,也不再读取 `StatementKind`、`ExpressionKind`、`ValueType`、assignment pattern、源 index/shape、节点 location/line/origin、builtin binding、call transfer 或动态参数集合,且不接收 `TranspileOptions`、runtime requirements、函数图;两个目标 runtime source catalog 与 representation planner/verifier 均为独立编译单元。尚未结构化的一般 RAII/copy-move/runtime-call node 仍在对应 target renderer 序列化;核心只持有 opaque target artifact,两个 emitter 只调用 `serialize_chunks`。 - facade 从最终 LIR origin 构建 source map v3,并公开 dependency manifest 和包含阶段耗时/节点/峰值 arena、逐 pass 指标、MIR 变换 before/after 计数,以及 flow/anti/output/barrier/loop-carried 内存依赖统计的编译报告。 当前 memory-dependence 基础没有被冒充为 MemorySSA 或优化完成:能够表达 RAW/WAR/WAW、分支合流、循环携带、unknown barrier 和静态 region no-alias,但尚未建立 memory version/phi/def-use rename,也未启用 region-aware DCE、store forwarding 或循环内存变换。全局值编号、一般 phi/SCCP、浮点代数和跨函数优化同样未宣称完成。独立 target AST、一般 RAII/copy-move/runtime ABI node、完整四语言官方 grammar、跨语言动态 shape 数据流与一般 NDArray/typed-array 所有权、跨动态 extent/一般 view/pointer 的完整区域组合及稳定插件 ABI 仍未完成。所有边界逐项记录在 [TODO](../TODO.md)。 -Matlab 索引写入由 Analyzer 唯一生成 `IndexedMutationContract`:`overwrite`、Python `resize`、Matlab `grow` 与 `erase` 均显式保存 static/runtime shape source、线性列主序身份、删除轴和输入/结果 shape。Semantic v21、MIR v27 和双目标 LIR v34 分别验证 rank、shape 变化方向与 axis;growth/deletion 在 MIR 中一律成为整个 storage root 的写入,后端只执行计划。内存依赖分析先形成当前写入所需的 RAW/WAR/WAW,再以 full-root write 裁剪被覆盖的同根历史,避免长期 mutation 序列出现二次 frontier 增长。JavaScript 使用 checked nested-array resize/axis erase,`cpp` 使用 typed nested-`std::vector` template 独立实现;Matlab null assignment 只允许 vector 线性删除或恰好一个非 colon selector 的整轴删除,静态 Analyzer 与动态 runtime 都拒绝非 vector 线性删除和多个非 colon selector。 +Matlab 索引写入由 Analyzer 唯一生成 `IndexedMutationContract`:`overwrite`、Python `resize`、Matlab `grow` 与 `erase` 均显式保存 static/runtime shape source、线性列主序身份、删除轴和输入/结果 shape。Semantic v22、MIR v28 和双目标 LIR v35 分别验证 rank、shape 变化方向与 axis;growth/deletion 在 MIR 中一律成为整个 storage root 的写入,后端只执行计划。内存依赖分析先形成当前写入所需的 RAW/WAR/WAW,再以 full-root write 裁剪被覆盖的同根历史,避免长期 mutation 序列出现二次 frontier 增长。JavaScript 使用 checked nested-array resize/axis erase,`cpp` 使用 typed nested-`std::vector` template 独立实现;Matlab null assignment 只允许 vector 线性删除或恰好一个非 colon selector 的整轴删除,静态 Analyzer 与动态 runtime 都拒绝非 vector 线性删除和多个非 colon selector。 零 extent shape 是语义事实而不是容器结构的副产物。JavaScript runtime 以不可枚举 `Symbol` descriptor 保存静态已知 shape,并在 reshape、transpose、broadcast、section 与 mutation 边界重新验证数据量和可结构观察的前缀;C++17 LIR 将 input/result shape 直接交给 length、transpose、broadcast 和 growth helper,并为 empty outer vector 补齐静态递归 rank。当前契约完整覆盖编译期已知的零 extent;跨函数动态 C++ 数组仍需统一 NDArray ABI 才能携带不可从 `std::vector` 恢复的尾随 extent。 Matlab complex 基础纵切面只授权 binary64 complex,不把 complex 冒充为 coarse `ValueType` 的新结构类别。scanner 将无空白 trailing `i`/`j` 数字识别为 imaginary literal,intrinsic catalog 将未遮蔽的 `i`/`j` 解析为 `imaginary_unit`;普通用户 binding 仍可遮蔽它们。Analyzer 对 scalar arithmetic、element-wise compatible-size array arithmetic、索引写入、reshape、`complex`/`conj`/`real`/`imag`/`abs` 和两种 transpose 生成 numeric side-table 事实;跨函数未知 complexity 使用 dynamic numeric helper。JavaScript complex division采用按分母尺度归一化的 object kernel,C++ runtime 使用相同稳定算法而不直接依赖实现定义的 `std::complex` 中间溢出行为;零指数显式返回单位元。complex comparison、logical 与 `all`/`any` 仍由 Analyzer 以 `MPF2053` 拒绝。二维稠密 complex matrix multiply、square left/right divide 和 safe-integer power 则必须携带显式 complex matrix domain,不能落入 real solver policy。 -Matlab 稠密实数方阵除法由 Semantic v21 的 `MatrixStructurePolicy::classify_real_square` 明确选择运行时结构检测,MIR v27 与两个目标 LIR v34 原样传播并逐层验证;renderer 不读取 shape 或 helper 名称推导算法。JavaScript 与 `cpp` runtime 各自只求值一次系数矩阵,并按固定优先级分类:exact-zero diagonal 直接逐元素除法,upper/lower-triangular 使用前向/回代,full tridiagonal 使用与 LAPACK `DGTTRF/DGTTRS` 等价的相邻行部分主元紧凑 LU,非三对角 exact-symmetric positive-definite 使用 Cholesky,其余方阵回退到部分主元 dense LU。对称候选若 Cholesky 检测到非正定不会输出部分结果,而是进入 dense fallback。右除通过转置后的同一结构分派保持左右语义一致;每条路径使用与自身正/转置求解器一致的迭代 1-范数倒条件估计和同一 square-warning policy。实数路径不声明容差结构识别;R2024 full symmetric-indefinite 的 dense LU 行为属于当前 fallback contract。 +Matlab 稠密实数方阵除法由 Semantic v22 的 `MatrixStructurePolicy::classify_real_square` 明确选择运行时结构检测,MIR v28 与两个目标 LIR v35 原样传播并逐层验证;renderer 不读取 shape 或 helper 名称推导算法。JavaScript 与 `cpp` runtime 各自只求值一次系数矩阵,并按固定优先级分类:exact-zero diagonal 直接逐元素除法,upper/lower-triangular 使用前向/回代,full tridiagonal 使用与 LAPACK `DGTTRF/DGTTRS` 等价的相邻行部分主元紧凑 LU,非三对角 exact-symmetric positive-definite 使用 Cholesky,其余方阵回退到部分主元 dense LU。对称候选若 Cholesky 检测到非正定不会输出部分结果,而是进入 dense fallback。右除通过转置后的同一结构分派保持左右语义一致;每条路径使用与自身正/转置求解器一致的迭代 1-范数倒条件估计和同一 square-warning policy。实数路径不声明容差结构识别;R2024 full symmetric-indefinite 的 dense LU 行为属于当前 fallback contract。 -Matlab sparse 纵切面由 `ArrayStorageFormat::sparse_csc`、`SparseConstructionPlan`、`SparseIndexPlan`、`SparseMutationPlan`、`SparseReshapePlan`、`SparseElementwisePlan`、`MatrixStoragePolicy::sparse_csc_coefficient`/`sparse_csc_multiply`/`sparse_csc_scale`、`sparse_row_pivoted_lu` 和 `classify_sparse_real_square` 联合授权。R2024 的 `sparse(A)`、`sparse(m,n)`、`sparse(i,j,v)`、`sparse(i,j,v,m,n)` 与 `sparse(i,j,v,m,n,nzmax)` 在当前静态 real rank-2 contract(包含零 extent)内分别形成 dense conversion、zero matrix、inferred triplets、sized triplets 与 reserved triplets;construction plan 固定 result shape、三个 triplet cardinality 和 reserve hint。索引读取另由 `SparseIndexPlan` 固定 linear element、subscript element、linear selection 或 Cartesian submatrix selection,并携带输入/结果 shape 与 source/result storage;Semantic v21、MIR v27、JavaScript LIR v34 与 `cpp` LIR v34 会独立重算 identity、arity、type、shape、storage 和 inactive state。一个 selector 支持 scalar、full colon、slice、保序/重复/空 numeric 与 logical selection;两个 selector 支持相同类别的行列 Cartesian product。scalar 结果是 dense scalar,其余结果保持 canonical CSC;线性结果遵循 Matlab 的 selector/source-vector 方向规则,logical matrix 与 full colon 产生 column,二维 numeric selector 保持自身 shape。两个目标 runtime 都直接读取 CSC:任意线性选择按选中坐标查询,子矩阵通过有序 row map 扫描选中 CSC 列,`A(:)` 直接把列偏移折叠为单列 row index,复杂度为 O(nnz),均不物化 dense source。索引写入另由 `SparseMutationPlan` 固定 linear/subscript assignment、linear/axis deletion、source/selection/replacement/result shape 与 dense/CSC storage,并明确 scalar expansion、重复坐标按列主序 last-write-wins 和 exact-zero erase;双目标 runtime 独立排序/折叠更新并与原 CSC 单次归并,复杂度为 O(nnz + k log k),验证全部 selector/replacement 后才事务式提交。section 写入、dense/sparse RHS、自别名 RHS、静态线性/逐维扩容、vector 线性删除和单轴 null deletion 均保持 canonical CSC;`nzmax` capacity 不作为 mutation 后的可观察兼容承诺。triplet runtime 继续支持 scalar expansion、显式空输入、列/行稳定排序、重复坐标累加和 exact-zero cancellation;`nzmax` 在 C++17 中只作为 capacity hint。real CSC 的 `.'` 与 `'` 均走目标专属 transpose;renderer 只序列化目标 LIR 已选择的 form/helper,source map 在 helper call 上保留索引表达式位置,任一目标均不读取另一目标产物。`full`、`issparse`、`nnz` 分别承担显式转换、表示查询和非零计数。CSC×CSC 乘法使用按结果列的稀疏 scatter accumulator,仅排序触及的 row 并丢弃 exact-zero cancellation;CSC×dense 与 dense×CSC 只遍历 CSC 非零项并直接累计 dense 结果,三条路径都不物化稀疏源。CSC×scalar 与 scalar×CSC 由 `sparse_csc_scale` 保留 canonical CSC:非零因子只遍历 nnz,零因子以 O(columns) 产生同 shape 空 CSC,exact-zero underflow 被移除。`SparseElementwisePlan` 与矩阵 `*` 的 `MatrixOperationPlan` 完全分离,显式固定 `.*` operation、preserve-sparse storage policy、左右 operand shape/storage、compatible-size broadcast axis 和结果 shape;Semantic v21、MIR v27 与两个目标 LIR v34 逐层重算这份合同。JavaScript/`cpp` representation planner 分别选择 CSC×scalar、scalar×CSC、CSC×dense、dense×CSC 或 CSC×CSC helper;目标独立 runtime 只遍历稀疏非零项或合并 CSC 列,支持 singleton row/column expansion,删除 exact-zero result 并直接产生 canonical CSC,不物化稀疏源。CSC 方阵求解对非空系数按 column pointer 选择 O(nnz) 三对角 band factorization 或有序 sparse-row 部分主元 LU,并由正/转置应用参与 1-范数条件估计;`0×0` 系数则按显式左/右/结果 shape 计划直接产生 `0×n` 或 `m×0` dense/CSC shaped-empty 结果。所有需要运行时 shape 的 helper 调用均由目标 LIR `runtime_shape_arguments` 固化,verifier 与 deterministic dump 覆盖完整实参序列,renderer 不得回读 semantic matrix/storage plan。以上已支持路径都保持静态零 extent。超过两个 selector、complex/sparse selector、产生 N-D 线性结果的 selector、动态或 complex source,以及 sparse logical/power/rectangular solve 仍在 Analyzer 以 `MPF2054` 失败关闭;runtime 仍验证 CSC pointer、严格递增 row index、有限非零 value、selector bounds、mutation replacement/shape/policy、标量乘法的 finite scalar/result、矩阵乘法/逐元素乘法/reshape 计划 shape、triplet contract 与 solve 的三组 shape ABI,防止外部伪造值绕过静态合同。 +Matlab sparse 纵切面由 `ArrayStorageFormat::sparse_csc`、`SparseConstructionPlan`、`SparseValueDomain`、`SparseDuplicatePolicy`、`SparseIndexPlan`、`SparseMutationPlan`、`SparseReshapePlan`、`SparseElementwisePlan`、`MatrixStoragePolicy::sparse_csc_coefficient`/`sparse_csc_multiply`/`sparse_csc_scale`、`sparse_row_pivoted_lu` 和 `classify_sparse_real_square` 联合授权。R2024 的 `sparse(A)`、`sparse(m,n)`、`sparse(i,j,v)`、`sparse(i,j,v,m,n)` 与 `sparse(i,j,v,m,n,nzmax)` 在当前静态 real/logical rank-2 值输入 contract(包含零 extent)内分别形成 dense conversion、zero matrix、inferred triplets、sized triplets 与 reserved triplets;construction plan 固定 value domain、numeric-sum/logical-any duplicate policy、result shape、三个 triplet cardinality 和 reserve hint;无值输入的 `sparse(m,n)` 保持 double。索引读取另由 `SparseIndexPlan` 固定 linear element、subscript element、linear selection 或 Cartesian submatrix selection,并携带输入/结果 shape 与 source/result storage;Semantic v22、MIR v28、JavaScript LIR v35 与 `cpp` LIR v35 会独立重算 identity、arity、type、shape、storage 和 inactive state。一个 selector 支持 scalar、full colon、slice、保序/重复/空 numeric 与 logical selection;两个 selector 支持相同类别的行列 Cartesian product。scalar 结果是 dense scalar,其余结果保持 canonical CSC;线性结果遵循 Matlab 的 selector/source-vector 方向规则,logical matrix 与 full colon 产生 column,二维 numeric selector 保持自身 shape。两个目标 runtime 都直接读取 CSC:任意线性选择按选中坐标查询,子矩阵通过有序 row map 扫描选中 CSC 列,`A(:)` 直接把列偏移折叠为单列 row index,复杂度为 O(nnz),均不物化 dense source。索引写入另由 `SparseMutationPlan` 固定 linear/subscript assignment、linear/axis deletion、source/selection/replacement/result shape 与 dense/CSC storage,并明确 scalar expansion、重复坐标按列主序 last-write-wins 和 exact-zero erase;双目标 runtime 独立排序/折叠更新并与原 CSC 单次归并,复杂度为 O(nnz + k log k),验证全部 selector/replacement 后才事务式提交。section 写入、dense/sparse RHS、自别名 RHS、静态线性/逐维扩容、vector 线性删除和单轴 null deletion 均保持 canonical CSC;`nzmax` capacity 不作为 mutation 后的可观察兼容承诺。triplet runtime 继续支持 scalar expansion、显式空输入、列/行稳定排序、numeric 重复坐标求和/抵消和 logical 重复坐标 `any` 聚合;`nzmax` 在 C++17 中只作为 capacity hint。real/logical CSC 的 `.'` 与 `'` 均走保持值域的目标专属 transpose;renderer 只序列化目标 LIR 已选择的 form/helper,source map 在 helper call 上保留索引表达式位置,任一目标均不读取另一目标产物。`full`、`issparse`、`nnz` 分别承担显式转换、表示查询和非零计数。CSC×CSC 乘法使用按结果列的稀疏 scatter accumulator,仅排序触及的 row 并丢弃 exact-zero cancellation;CSC×dense 与 dense×CSC 只遍历 CSC 非零项并直接累计 dense 结果,三条路径都不物化稀疏源。CSC×scalar 与 scalar×CSC 由 `sparse_csc_scale` 保留 canonical CSC:非零因子只遍历 nnz,零因子以 O(columns) 产生同 shape 空 CSC,exact-zero underflow 被移除。`SparseElementwisePlan` 与矩阵 `*` 的 `MatrixOperationPlan` 完全分离,显式固定 `.*` operation、preserve-sparse storage policy、左右 operand shape/storage、compatible-size broadcast axis 和结果 shape;Semantic v22、MIR v28 与两个目标 LIR v35 逐层重算这份合同。JavaScript/`cpp` representation planner 分别选择 CSC×scalar、scalar×CSC、CSC×dense、dense×CSC 或 CSC×CSC helper;目标独立 runtime 只遍历稀疏非零项或合并 CSC 列,支持 singleton row/column expansion,删除 exact-zero result 并直接产生 canonical CSC,不物化稀疏源。CSC 方阵求解对非空系数按 column pointer 选择 O(nnz) 三对角 band factorization 或有序 sparse-row 部分主元 LU,并由正/转置应用参与 1-范数条件估计;`0×0` 系数则按显式左/右/结果 shape 计划直接产生 `0×n` 或 `m×0` dense/CSC shaped-empty 结果。所有需要运行时 shape 或 logical construction policy 的 helper 调用均由目标 LIR `runtime_shape_arguments`/`runtime_integer_arguments` 固化,verifier 与 deterministic dump 覆盖完整实参序列,renderer 不得回读 semantic matrix/storage plan。以上已支持路径都保持静态零 extent;logical construction、transpose、selection、mutation、reshape 与 `full` 保持 logical class,JavaScript 以带 `valueDomain` 的 tagged CSC 表示,`cpp` 以 `sparse_matrix` 表示。超过两个 selector、complex/sparse selector、产生 N-D 线性结果的 selector、动态或 complex source,以及 sparse logical 算术/逐元素逻辑/归约、power/rectangular solve 仍在 Analyzer 以 `MPF2054` 失败关闭;runtime 仍验证 CSC pointer、严格递增 row index、与值域一致的 canonical stored value、selector bounds、mutation replacement/shape/policy、标量乘法的 finite scalar/result、矩阵乘法/逐元素乘法/reshape 计划 shape、triplet contract 与 solve 的三组 shape ABI,防止外部伪造值绕过静态合同。 复数方阵由同一 `MatrixOperationPlan` 的 `MatrixNumericDomain::complex` 与 `MatrixStructurePolicy::classify_complex_square` 独立授权。两个目标先做 exact Hermitian 检测,完整 Cholesky 成功的正定矩阵走复数前向/共轭回代,其余矩阵确定性回退按复数模选主元的 dense LU;多列 RHS 共用一次因子分解。右除严格按 `(A'\B')'` 的共轭转置恒等式复用方阵求解,正/负 safe-integer 幂使用平方求幂,负幂先通过同一求解器求逆。两条分派都计算 1-范数倒条件估计并复用 singular/nearly-singular warning contract。复数矩形求解必须携带 `MatrixFactorizationPolicy::rank_revealing_column_pivoted_qr`,两个目标使用确定性列主元、复数 Householder reflector 和同一 working-precision rank tolerance;超定与欠定系统返回 pivoted basic least-squares solution,多 RHS 共用一次分解,秩亏稳定警告,右除通过共轭转置复用同一 kernel。complex sparse、容差 Hermitian 分类和非整数矩阵幂仍失败关闭。 -标量除法的结果类型与除零行为是两个独立源语义合同。HIR v2 `Profile` 以 `Division` 固定 native/real quotient,以 `DivisionByZero` 固定 target-native、IEEE-754 或 exception;Python 选择 real quotient + exception,Matlab/TypeScript 选择 real quotient + IEEE-754,Fortran 当前保持 target-native。MIR v27 原样传播 profile,双目标 LIR v34 再分别选择目标专属 `binary_runtime_call` 或原生 ECMAScript IEEE 运算。C++17 的 IEEE 路径也必须跨越参数化 runtime helper,避免 MSVC 把惰性分支中的字面量零除在编译期拒绝;Python `/` 与 `//` 在 JavaScript 和 C++17 runtime 中先检查零分母并给出稳定错误。renderer 只序列化已验证的 helper token,不读取源语言、除零策略或 helper 名称;旧的 C++ direct-scalar-division LIR form 已移除。 +标量除法的结果类型与除零行为是两个独立源语义合同。HIR v2 `Profile` 以 `Division` 固定 native/real quotient,以 `DivisionByZero` 固定 target-native、IEEE-754 或 exception;Python 选择 real quotient + exception,Matlab/TypeScript 选择 real quotient + IEEE-754,Fortran 当前保持 target-native。MIR v28 原样传播 profile,双目标 LIR v35 再分别选择目标专属 `binary_runtime_call` 或原生 ECMAScript IEEE 运算。C++17 的 IEEE 路径也必须跨越参数化 runtime helper,避免 MSVC 把惰性分支中的字面量零除在编译期拒绝;Python `/` 与 `//` 在 JavaScript 和 C++17 runtime 中先检查零分母并给出稳定错误。renderer 只序列化已验证的 helper token,不读取源语言、除零策略或 helper 名称;旧的 C++ direct-scalar-division LIR form 已移除。 -逻辑求值同样不由 renderer 猜测。Semantic v21 的 `LogicalEvaluation` 为表达式指定 `eager_elementwise`、`short_circuit_boolean` 或 `short_circuit_operand`;MIR v27 据此决定直接求值或显式 CFG/typed merge,并在 Matlab boolean-short-circuit 路径把 operand 规范成 scalar logical,而 Python operand-short-circuit 仍合流原值。两个目标 LIR v34 原样保存策略,再分别选择 compatible-size logical runtime、scalar short-circuit 或 unary logical helper。Matlab statement condition 另由 profile 固定为“非空且所有元素非零”,`&`/`|` 只有在 condition logical-composition 上下文才采用 scalar 短路;数组必须通过 `all`/`any` 显式归约。Semantic/MIR/LIR dump 和逐层 verifier 都重算 operator/context 关系,防止符号 identity、eager/lazy 策略或 truthiness 在后端分叉后漂移。 +逻辑求值同样不由 renderer 猜测。Semantic v22 的 `LogicalEvaluation` 为表达式指定 `eager_elementwise`、`short_circuit_boolean` 或 `short_circuit_operand`;MIR v28 据此决定直接求值或显式 CFG/typed merge,并在 Matlab boolean-short-circuit 路径把 operand 规范成 scalar logical,而 Python operand-short-circuit 仍合流原值。两个目标 LIR v35 原样保存策略,再分别选择 compatible-size logical runtime、scalar short-circuit 或 unary logical helper。Matlab statement condition 另由 profile 固定为“非空且所有元素非零”,`&`/`|` 只有在 condition logical-composition 上下文才采用 scalar 短路;数组必须通过 `all`/`any` 显式归约。Semantic/MIR/LIR dump 和逐层 verifier 都重算 operator/context 关系,防止符号 identity、eager/lazy 策略或 truthiness 在后端分叉后漂移。 -`all`/`any` 也不是普通 helper 名称映射。Analyzer 生成的 `ReductionPlan` 区分默认首个非 singleton 维、常量 `dim`、常量 `vecdim` 与 `'all'`,并在静态 shape 上直接计算输出 extent;高于 rank 的维度是 no-op,`0×0` 默认归约分别使用 `all=true`、`any=false` identity。Semantic v21、MIR v27 与两个目标 LIR v34 独立重算 intrinsic、axis 和 shape 关系,优化 tombstone 必须清空 plan。JavaScript 通过带 shape descriptor 的列主序 kernel 保留不可结构恢复的零 extent,`cpp` 通过静态 shape 模板参数和递归 `std::vector` kernel 独立生成相同结果;未知 rank 只为 `'all'` 使用 runtime total reduction。当前字符数组、动态 `dim`/`vecdim` 与未知 rank 的维度保持型归约以 `MPF2052` 失败关闭,等待统一 NDArray/numeric-class 模型,不由 emitter 猜测。 +`all`/`any` 也不是普通 helper 名称映射。Analyzer 生成的 `ReductionPlan` 区分默认首个非 singleton 维、常量 `dim`、常量 `vecdim` 与 `'all'`,并在静态 shape 上直接计算输出 extent;高于 rank 的维度是 no-op,`0×0` 默认归约分别使用 `all=true`、`any=false` identity。Semantic v22、MIR v28 与两个目标 LIR v35 独立重算 intrinsic、axis 和 shape 关系,优化 tombstone 必须清空 plan。JavaScript 通过带 shape descriptor 的列主序 kernel 保留不可结构恢复的零 extent,`cpp` 通过静态 shape 模板参数和递归 `std::vector` kernel 独立生成相同结果;未知 rank 只为 `'all'` 使用 runtime total reduction。当前字符数组、动态 `dim`/`vecdim` 与未知 rank 的维度保持型归约以 `MPF2052` 失败关闭,等待统一 NDArray/numeric-class 模型,不由 emitter 猜测。 稀疏 reshape 由独立 `SparseReshapePlan` 授权,不复用 dense reshape 的容器推断。Analyzer 区分 size vector 与逗号分隔维度列表,在静态总元素数上解析至多一个 `[]`,并保存输入、原始请求和 -二维结果 shape;请求 rank 大于 2 时保留首维,其余维度乘入第二维。Semantic v21、MIR v27、 -JavaScript LIR v34 与 `cpp` LIR v34 分别重算 syntax identity、推断轴、shape 乘积、CSC storage +二维结果 shape;请求 rank 大于 2 时保留首维,其余维度乘入第二维。Semantic v22、MIR v28、 +JavaScript LIR v35 与 `cpp` LIR v35 分别重算 syntax identity、推断轴、shape 乘积、CSC storage 和 inactive state。两个目标 runtime 都按原 CSC 的列主序遍历次序将 `row + column*oldRows` 映射为新行列,复杂度为 O(nnz + result-columns),无需排序或 dense materialization;runtime 再次验证 canonical CSC、输入/请求/结果 shape 与折叠关系。renderer 只序列化目标 LIR 已绑定 diff --git a/docs/COMPILER_PIPELINE.md b/docs/COMPILER_PIPELINE.md index 78791ef..d67eff9 100644 --- a/docs/COMPILER_PIPELINE.md +++ b/docs/COMPILER_PIPELINE.md @@ -159,12 +159,12 @@ arena 只通过强类型 ID 连接,目标后端必须 O(1) lookup 后构造自 ### 类型、shape 与 storage - `TypeId` 描述标量、tuple、sequence、array、function/reference 等逻辑类型。 -- `NumericType` 是 `TypeId` 内与 coarse `ValueType` 正交的数值 contract:`NumericClass` 当前包含 logical、signed-integer、binary64,`NumericComplexity` 包含 real、complex;`unknown` 表示必须延迟到运行时的参数相关表示,不能被 type interner 当成 real 默认值。expression/element/tuple/statement/parameter/return/multi-target 都必须有稠密 side-table 行,Semantic v21、MIR v27 和双目标 LIR v34 分别验证 class/complexity 组合与结构类型的一致性。 +- `NumericType` 是 `TypeId` 内与 coarse `ValueType` 正交的数值 contract:`NumericClass` 当前包含 logical、signed-integer、binary64,`NumericComplexity` 包含 real、complex;`unknown` 表示必须延迟到运行时的参数相关表示,不能被 type interner 当成 real 默认值。expression/element/tuple/statement/parameter/return/multi-target 都必须有稠密 side-table 行,Semantic v22、MIR v28 和双目标 LIR v35 分别验证 class/complexity 组合与结构类型的一致性。 - `ArrayStorageFormat` 与数值类别、complexity 和 shape 正交,当前显式区分 `none`、`unknown`、`dense` 与 canonical `sparse_csc`。expression/statement/tuple element/parameter/return/multi-target 必须具有稠密 storage side-table 行;控制流 join 只在两侧 storage 相同或一侧为未知时保留确定事实,不能把 dense/sparse 分歧猜成任一具体表示。 - `ShapeId` 独立描述 rank、静态/动态 extent、stride、layout 和 section view。 - `StorageId` 表示可能共享或重叠的存储区域;view、optional parameter、copy-in/copy-out、writable actual 和 lifetime 必须显式关联。 - `StorageRegion` 是不修改 HIR 结构节点的稠密 semantic/operation side-table fact:`rectangular` 保存 root shape 及每维零基 `first/stride/count`,`linearized` 保存列主序单 selector 的线性 progression,`unknown` 明确表示不能作精确结论。 -- MIR v27 不把区域或 effect 塞回 `Instruction`;revision-bound `OperationAttributeTable` 按 `InstructionId` 稠密保存 `InstructionAttributes`,每条指令可持有零到多个 `MemoryAccess {storage, root, region, mode}`。expression 属性以强类型 `BroadcastPlan` 区分 Matlab `static_extents`/`runtime_operands` shape source 并保存可用的逐轴 mode,以 `ReductionPlan` 保存 logical operation、axis policy、static/runtime shape source、归约轴和输入/输出 shape ID,以 `SparseConstructionPlan` 保存 constructor kind、result shape ID、triplet cardinality 与 reserve hint,以 `SparseIndexPlan` 保存 linear/subscript scalar 或 storage-preserving selection identity、输入/结果 shape ID 与 storage,以 `SparseMutationPlan` 保存 linear/subscript assignment、linear/axis deletion、四类 shape ID、dense/CSC replacement storage、scalar expansion、last-write-wins 与 zero erase policy,以 `SparseReshapePlan` 保存 size-vector/dimension-list identity、单个推断轴、source/result storage 和 input/requested/result shape ID,以 `SparseElementwisePlan` 保存 `.*` operation、preserve-sparse policy、左右 storage/shape、逐轴 compatible-size broadcast mode 和结果 shape,以 `MatrixOperationPlan` 保存 matrix kind、real/complex numeric domain、dense/CSC coefficient storage policy、square/overdetermined/underdetermined solve kind、condition/factorization/structure policy 和输入/输出 shape ID,以逐下标 `IndexSelectorKind` 保存 scalar/slice/numeric/logical/empty identity,并以 `IndexExtentSource` 保存静态、运行时 axis 或运行时 linear extent 来源。Matlab empty literal 则由 Semantic v21 固定为 real、dense、column-major、`0×0` sequence,目标 LIR v34 再用 `ArrayLiteralPlan` 区分 direct 与 shaped-empty 表示。直接 load/index/slice 产生 read,store/store-indexed/loop-variable/writeback 产生 write,copy-in/out 只在 copy-in 阶段读取 original region,copy-out 不伪造读取;调用自身的跨函数读写在 alias/effect fixed point 中按 actual region 实例化。 +- MIR v28 不把区域或 effect 塞回 `Instruction`;revision-bound `OperationAttributeTable` 按 `InstructionId` 稠密保存 `InstructionAttributes`,每条指令可持有零到多个 `MemoryAccess {storage, root, region, mode}`。expression 属性以强类型 `BroadcastPlan` 区分 Matlab `static_extents`/`runtime_operands` shape source 并保存可用的逐轴 mode,以 `ReductionPlan` 保存 logical operation、axis policy、static/runtime shape source、归约轴和输入/输出 shape ID,以 `SparseConstructionPlan` 保存 constructor kind、value domain、duplicate policy、result shape ID、triplet cardinality 与 reserve hint,以 `SparseIndexPlan` 保存 linear/subscript scalar 或 storage-preserving selection identity、输入/结果 shape ID 与 storage,以 `SparseMutationPlan` 保存 linear/subscript assignment、linear/axis deletion、四类 shape ID、dense/CSC replacement storage、scalar expansion、last-write-wins 与 zero erase policy,以 `SparseReshapePlan` 保存 size-vector/dimension-list identity、单个推断轴、source/result storage 和 input/requested/result shape ID,以 `SparseElementwisePlan` 保存 `.*` operation、preserve-sparse policy、左右 storage/shape、逐轴 compatible-size broadcast mode 和结果 shape,以 `MatrixOperationPlan` 保存 matrix kind、real/complex numeric domain、dense/CSC coefficient storage policy、square/overdetermined/underdetermined solve kind、condition/factorization/structure policy 和输入/输出 shape ID,以逐下标 `IndexSelectorKind` 保存 scalar/slice/numeric/logical/empty identity,并以 `IndexExtentSource` 保存静态、运行时 axis 或运行时 linear extent 来源。Matlab empty literal 则由 Semantic v22 固定为 real、dense、column-major、`0×0` sequence,目标 LIR v35 再用 `ArrayLiteralPlan` 区分 direct 与 shaped-empty 表示。直接 load/index/slice 产生 read,store/store-indexed/loop-variable/writeback 产生 write,copy-in/out 只在 copy-in 阶段读取 original region,copy-out 不伪造读取;调用自身的跨函数读写在 alias/effect fixed point 中按 actual region 实例化。 - `MatrixOperationPlan` 以 `MatrixNumericDomain` 固定 real/complex kernel family,以 `MatrixStoragePolicy` 固定 dense、sparse-CSC coefficient、sparse-CSC product 或 sparse-CSC scale contract,以 `MatrixFactorizationPolicy` 固定 algorithm family,并以 `MatrixStructurePolicy` 固定允许的结构检测:dense real square solve 必须携带 `classify_real_square`,dense complex square solve 必须携带 `classify_complex_square`,real/complex rectangular solve 必须携带 `rank_revealing_column_pivoted_qr`,sparse real square solve 必须携带 `sparse_csc_coefficient`、`sparse_row_pivoted_lu` 和 `classify_sparse_real_square`;静态 finite-real rank-2 乘法只要一侧为 CSC 就必须携带 `sparse_csc_multiply`,并按 sparse×sparse→CSC、混合乘法→dense 固定结果 storage;一侧为 CSC、另一侧为 finite real/logical scalar 时必须携带 `sparse_csc_scale`,双向次序都固定为 canonical CSC 结果;dense multiply 与 power 使用 dense/none 对应合同。SemanticTable、MIR verifier 和两个目标 LIR verifier 都从 typed operands、storage、operation 和 solve kind 独立重算这些关系;retired/inactive 节点必须清空 domain、storage、policy 和 shape,避免 target helper 名称成为隐式语义通道。新增 storage 或 algorithm 分派必须先增加 typed contract,不能借用已有枚举静默分派。 - alias 结果不内嵌 `StorageData`,而在稀疏 side table 中使用 `no_alias`、`may_alias`、`must_alias` 等保守格;未知时不能假设不重叠。 - mutable aggregate 不强行伪装为纯 SSA 值;通过 value SSA 加显式 memory/storage effect 表达,后续可演进 memory SSA。 @@ -290,9 +290,9 @@ Target Semantic IR 是各后端私有的强类型。不能使用一个共享结 名称分配、声明排序、runtime dependency pruning、临时值 canonicalization 和 target peephole pass 在这里完成。每个 pass 必须声明 preserved analysis 并接受对应 LIR verifier。 -当前 LIR v34 已把函数 calling convention、source export、临时值、顶层/函数/statement/body/alternative 作用域声明、顶层布局、表达式/语句、numeric class/complexity、array storage、当前 writable-call ownership/copy-in-out、强类型 comparison、logical-evaluation/truthiness、scalar-division/zero-denominator policy、Matlab array-literal/broadcast/reduction shape source、sparse-construction kind/result-shape/cardinality/reserve、sparse-index kind/input-result-shape/storage、sparse-mutation kind/replacement/duplicate/zero-policy 与四类 shape/storage、sparse-reshape kind/dimension-form/inference/axis 与 input/requested/result shape/storage、sparse-elementwise operation/storage/broadcast/shape、matrix-operation kind/numeric-domain/storage-policy/solve/condition-policy/factorization-policy/structure-policy/shape、逐下标 selector/runtime-extent identity、`runtime_shape_arguments` 调用 ABI 和逐节点 source mapping representation 前移。expression、statement、parameter、return、multi-target 与 C++ declaration 保存 `SymbolId` 和对应 numeric/storage contract,`IdentifierInventory` 验证 identity/spelling 后生成目标保留字安全名称;不同 lexical scope 的同名 symbol 可保持可读 spelling,但 renderer 不能再以 spelling 推导绑定。每个 call argument 使用一个聚合 plan 保存 value/optional-forward/reference-box/copy-section 与 none/direct/element/section writeback,避免并行数组;`ComparisonForm` 明确选择 infix/dynamic/structural equality/identity/membership,`EvaluationForm` 与 call value/outcome 明确选择 JavaScript arrow IIFE/thunk 或 C++ reference lambda、first-result 和结果保存。Matlab 已知 complex 选择目标专属 complex call,参数相关 unknown complexity 选择 dynamic numeric call;complex matrix domain 另选择独立 matrix runtime fragment。JavaScript LIR 绑定 object runtime,`cpp` LIR 绑定 `std::complex` runtime,real-only 模块不声明 complex feature 或 complex-matrix fragment。标量除法由 representation planner 选择 JavaScript Python checked helper、ECMAScript IEEE infix、C++ Python checked helper 或 C++ IEEE helper;C++ LIR 不保留可绕过运行时合同的 direct real/floor/reverse division form。Matlab runtime-operands broadcast 由目标独立推导矩形 shape、规范化 row vector/trailing singleton 并验证兼容性;logical reduction 直接消费 operation/axis/shape plan,静态维度保持输出 rank,未知 rank 只允许全维 total reduction;shaped-empty array literal、静态 section result、transpose、length 与 growth 直接消费目标 plan 的 shape,不从嵌套容器反推不可见的零 extent;动态 `end` 由 `IndexExtentSource` 明确选择当前轴长度或列主序 numel。矩形 real/complex solve 只能选择 rank-aware basic-solution 与 CPQR factorization policy,双目标 runtime 独立执行分解、秩判断和 warning;dense real/complex 方阵选择 square-continue-with-warning policy,并分别消费 real transpose 或 complex conjugate-transpose 结构求解与 1-范数条件估计;sparse mutation 由双目标各自执行 CSC update sort/collapse/merge,全部验证后才提交且不物化 sparse source;sparse CSC 实数方阵则选择 sparse-row-pivoted-LU contract,目标 runtime 独立验证 canonical CSC,并按三对角 O(nnz) 快路或一般稀疏行主元 LU 求解而不物化稠密系数副本,`0×0` 系数按三组 shape ABI 直接产生 dense/CSC shaped-empty 结果;sparse product 由 JavaScript/`cpp` 各自把同一 storage plan 绑定到三种 matrix-product target helper,CSC×CSC 走按列 sparse accumulator,混合乘法只遍历 CSC 非零项且直接产生 dense 结果;scalar product 另按操作数方向绑定两个 scale helper,非零因子以 O(nnz) 生成 canonical CSC,零因子以 O(columns) 生成同 shape 空 CSC;sparse element-wise product 独立绑定 CSC×scalar、scalar×CSC、CSC×dense、dense×CSC 或 CSC×CSC helper,按计划执行 compatible-size expansion 并保持 canonical CSC。C++ 类型探针保持严格 C++17,`cpp` 二元 comparison 同样拥有两个稠密 operand temporary,保证左到右单次求值。`SourceSegmentPlan` 按 `LirNodeId` 稠密保存 source/origin,renderer 只能用 ID 查询并产生 marker。独立 representation verifier 在 ABI/resource planner 后重建 expression、statement、scope、numeric/storage contract、comparison、division/runtime call、array literal、broadcast/reduction/sparse construction/sparse index/sparse mutation/sparse reshape/sparse elementwise/matrix operation、runtime shape arguments、selector、extent、evaluation、writeback 和 source segment 计划;deterministic dump 公开 shape ABI。架构门禁禁止 renderer 恢复共享语义、按 spelling 查 binding、扫描 call argument、读取 semantic matrix/storage policy、读取节点 location/line/origin 或自行选择 wrapper。完整独立 target AST、一般 RAII/copy-move/runtime ABI node、结构化 import/export/chunk 与 generated-only segment 仍按 TODO 继续前移。 +当前 LIR v35 已把函数 calling convention、source export、临时值、顶层/函数/statement/body/alternative 作用域声明、顶层布局、表达式/语句、numeric class/complexity、array storage、当前 writable-call ownership/copy-in-out、强类型 comparison、logical-evaluation/truthiness、scalar-division/zero-denominator policy、Matlab array-literal/broadcast/reduction shape source、sparse-construction kind/value-domain/duplicate-policy/result-shape/cardinality/reserve、sparse-index kind/input-result-shape/storage、sparse-mutation kind/replacement/duplicate/zero-policy 与四类 shape/storage、sparse-reshape kind/dimension-form/inference/axis 与 input/requested/result shape/storage、sparse-elementwise operation/storage/broadcast/shape、matrix-operation kind/numeric-domain/storage-policy/solve/condition-policy/factorization-policy/structure-policy/shape、逐下标 selector/runtime-extent identity、`runtime_shape_arguments`/`runtime_integer_arguments` 调用 ABI 和逐节点 source mapping representation 前移。expression、statement、parameter、return、multi-target 与 C++ declaration 保存 `SymbolId` 和对应 numeric/storage contract,`IdentifierInventory` 验证 identity/spelling 后生成目标保留字安全名称;不同 lexical scope 的同名 symbol 可保持可读 spelling,但 renderer 不能再以 spelling 推导绑定。每个 call argument 使用一个聚合 plan 保存 value/optional-forward/reference-box/copy-section 与 none/direct/element/section writeback,避免并行数组;`ComparisonForm` 明确选择 infix/dynamic/structural equality/identity/membership,`EvaluationForm` 与 call value/outcome 明确选择 JavaScript arrow IIFE/thunk 或 C++ reference lambda、first-result 和结果保存。Matlab 已知 complex 选择目标专属 complex call,参数相关 unknown complexity 选择 dynamic numeric call;complex matrix domain 另选择独立 matrix runtime fragment。JavaScript LIR 绑定 object runtime,`cpp` LIR 绑定 `std::complex` runtime,real-only 模块不声明 complex feature 或 complex-matrix fragment。标量除法由 representation planner 选择 JavaScript Python checked helper、ECMAScript IEEE infix、C++ Python checked helper 或 C++ IEEE helper;C++ LIR 不保留可绕过运行时合同的 direct real/floor/reverse division form。Matlab runtime-operands broadcast 由目标独立推导矩形 shape、规范化 row vector/trailing singleton 并验证兼容性;logical reduction 直接消费 operation/axis/shape plan,静态维度保持输出 rank,未知 rank 只允许全维 total reduction;shaped-empty array literal、静态 section result、transpose、length 与 growth 直接消费目标 plan 的 shape,不从嵌套容器反推不可见的零 extent;动态 `end` 由 `IndexExtentSource` 明确选择当前轴长度或列主序 numel。矩形 real/complex solve 只能选择 rank-aware basic-solution 与 CPQR factorization policy,双目标 runtime 独立执行分解、秩判断和 warning;dense real/complex 方阵选择 square-continue-with-warning policy,并分别消费 real transpose 或 complex conjugate-transpose 结构求解与 1-范数条件估计;sparse mutation 由双目标各自执行 CSC update sort/collapse/merge,全部验证后才提交且不物化 sparse source;sparse CSC 实数方阵则选择 sparse-row-pivoted-LU contract,目标 runtime 独立验证 canonical CSC,并按三对角 O(nnz) 快路或一般稀疏行主元 LU 求解而不物化稠密系数副本,`0×0` 系数按三组 shape ABI 直接产生 dense/CSC shaped-empty 结果;sparse product 由 JavaScript/`cpp` 各自把同一 storage plan 绑定到三种 matrix-product target helper,CSC×CSC 走按列 sparse accumulator,混合乘法只遍历 CSC 非零项且直接产生 dense 结果;scalar product 另按操作数方向绑定两个 scale helper,非零因子以 O(nnz) 生成 canonical CSC,零因子以 O(columns) 生成同 shape 空 CSC;sparse element-wise product 独立绑定 CSC×scalar、scalar×CSC、CSC×dense、dense×CSC 或 CSC×CSC helper,按计划执行 compatible-size expansion 并保持 canonical CSC。C++ 类型探针保持严格 C++17,`cpp` 二元 comparison 同样拥有两个稠密 operand temporary,保证左到右单次求值。`SourceSegmentPlan` 按 `LirNodeId` 稠密保存 source/origin,renderer 只能用 ID 查询并产生 marker。独立 representation verifier 在 ABI/resource planner 后重建 expression、statement、scope、numeric/storage contract、comparison、division/runtime call、array literal、broadcast/reduction/sparse construction/sparse index/sparse mutation/sparse reshape/sparse elementwise/matrix operation、runtime shape/integer arguments、selector、extent、evaluation、writeback 和 source segment 计划;deterministic dump 公开 shape/integer ABI。架构门禁禁止 renderer 恢复共享语义、按 spelling 查 binding、扫描 call argument、读取 semantic matrix/storage policy、读取节点 location/line/origin 或自行选择 wrapper。完整独立 target AST、一般 RAII/copy-move/runtime ABI node、结构化 import/export/chunk 与 generated-only segment 仍按 TODO 继续前移。 -方阵 LIR binding 分别把 `classify_real_square`、`classify_complex_square` 与 `classify_sparse_real_square` 映射到目标自己的 structured-real-square、structured-complex-square 与 sparse-square helper。dense real helper 按 exact-value contract 依次选择 diagonal、upper/lower、pivoted tridiagonal、symmetric positive-definite Cholesky 或 dense LU;对称但非正定的候选必须回退 dense LU,右除通过转置复用同一分派。complex helper 对 exact Hermitian positive-definite 使用 Cholesky,否则使用按模选主元 dense LU;右除通过共轭转置复用求解,negative integer power 先调用同一 solver 求逆。`sparse` constructor binding 只能在已验证的 `SparseConstructionPlan` 与调用 arity 一致时选择目标 helper;JavaScript/C++ runtime 分别直接构建 canonical CSC,处理 scalar expansion、empty triplet、duplicate accumulation/cancellation、`nzmax` 与 real sparse transpose,renderer 不读取 plan。sparse index binding 只消费已验证的 `SparseIndexPlan`,区分 scalar 与 CSC-preserving linear/Cartesian-submatrix helper;目标 runtime 独立验证 selector、结果 shape 和 canonical CSC,`A(:)` 使用 O(nnz) 直接 remap,renderer 不从 selector 或 source shape 重建语义。sparse mutation binding 只消费已验证的 `SparseMutationPlan`,选择目标专属 assign/erase helper;runtime 对重复 selector 按列主序 last-write-wins、对 exact zero 删除 entry,并用 O(nnz + k log k) 的排序/归并事务式提交,renderer 不重建 assignment/deletion 语义。sparse solve helper 接受 canonical、静态 shape 的 real rank-two CSC coefficient(包含 `0×0`),并独立验证 column pointer、row ordering、stored values,以及 LHS/RHS/result 三组 shape ABI;非空系数走 sparse factorization,`0×0` 系数直接产生 `0×n` 或 `m×0` dense/CSC shaped-empty 结果。右除使用 sparse transpose identity,结果 storage 按源语言操作数规则确定。representation planner 将三组 shape 依调用顺序写入目标 `runtime_shape_arguments`,verifier 和 dump 重算完整 ABI,renderer 只序列化,不读取 semantic plan。矩形 binding 还必须同时匹配 real/complex numeric domain 与 CPQR factorization policy;复数路径使用 Hermitian Householder reflector,并由共轭转置右除复用一次分解。所有 helper 都拥有对应 condition/rank warning contract,JavaScript/C++ runtime 互不引用。C++ 嵌套矩阵字面量的统一数值元素类型由 representation planner 递归传播,renderer 只输出规划后的具体容器类型和 widening,不从子行重新推断类型。 +方阵 LIR binding 分别把 `classify_real_square`、`classify_complex_square` 与 `classify_sparse_real_square` 映射到目标自己的 structured-real-square、structured-complex-square 与 sparse-square helper。dense real helper 按 exact-value contract 依次选择 diagonal、upper/lower、pivoted tridiagonal、symmetric positive-definite Cholesky 或 dense LU;对称但非正定的候选必须回退 dense LU,右除通过转置复用同一分派。complex helper 对 exact Hermitian positive-definite 使用 Cholesky,否则使用按模选主元 dense LU;右除通过共轭转置复用求解,negative integer power 先调用同一 solver 求逆。`sparse` constructor binding 只能在已验证的 `SparseConstructionPlan` 与调用 arity 一致时选择目标 helper;JavaScript/C++ runtime 分别直接构建 canonical CSC,处理 scalar expansion、empty triplet、numeric duplicate 求和/抵消、logical duplicate `any`、`nzmax` 与保持值域的 sparse transpose,JavaScript 以 tagged CSC 保存 `valueDomain`,`cpp` 以 `sparse_matrix` 保存 logical entry;representation planner 将 value-domain/duplicate-policy 编码为目标 integer ABI 或 helper token,renderer 不读取 plan。sparse index binding 只消费已验证的 `SparseIndexPlan`,区分 scalar 与 CSC-preserving linear/Cartesian-submatrix helper;目标 runtime 独立验证 selector、结果 shape 和 canonical CSC,`A(:)` 使用 O(nnz) 直接 remap,renderer 不从 selector 或 source shape 重建语义。sparse mutation binding 只消费已验证的 `SparseMutationPlan`,选择目标专属 assign/erase helper;runtime 对重复 selector 按列主序 last-write-wins、对 exact zero 删除 entry,并用 O(nnz + k log k) 的排序/归并事务式提交,renderer 不重建 assignment/deletion 语义。sparse solve helper 接受 canonical、静态 shape 的 real rank-two CSC coefficient(包含 `0×0`),并独立验证 column pointer、row ordering、stored values,以及 LHS/RHS/result 三组 shape ABI;非空系数走 sparse factorization,`0×0` 系数直接产生 `0×n` 或 `m×0` dense/CSC shaped-empty 结果。右除使用 sparse transpose identity,结果 storage 按源语言操作数规则确定。representation planner 将三组 shape 依调用顺序写入目标 `runtime_shape_arguments`,verifier 和 dump 重算完整 ABI,renderer 只序列化,不读取 semantic plan。矩形 binding 还必须同时匹配 real/complex numeric domain 与 CPQR factorization policy;复数路径使用 Hermitian Householder reflector,并由共轭转置右除复用一次分解。所有 helper 都拥有对应 condition/rank warning contract,JavaScript/C++ runtime 互不引用。C++ 嵌套矩阵字面量的统一数值元素类型由 representation planner 递归传播,renderer 只输出规划后的具体容器类型和 widening,不从子行重新推断类型。 Matlab shape-changing write 由 statement plan 固化为 overwrite/resize/grow/erase;目标 plan 同时保存线性布局、删除轴与输入/结果 shape。JavaScript LIR 选择 checked nested-array growth/axis erase,`cpp` LIR 选择 typed nested-`std::vector` growth/axis erase。两者共享语义 contract,但运行时实现、renderer 和 verifier 互不引用。 diff --git a/docs/DIAGNOSTICS.md b/docs/DIAGNOSTICS.md index fef26a1..373cbd7 100644 --- a/docs/DIAGNOSTICS.md +++ b/docs/DIAGNOSTICS.md @@ -1,6 +1,6 @@ # 诊断与 CLI 契约 -MPF 当前为库调用、命令行、IDE 和 CI 使用同一个诊断模型;本文描述 0.6.8 源码树的唯一契约。每条诊断在构造时即包含 code、severity、消息、源文件身份以及完整的 1-based UTF-8 code-point range;renderer 不接收缺失结束位置的旧结构,也不合成兼容范围。 +MPF 当前为库调用、命令行、IDE 和 CI 使用同一个诊断模型;本文描述 0.6.9 源码树的唯一契约。每条诊断在构造时即包含 code、severity、消息、源文件身份以及完整的 1-based UTF-8 code-point range;renderer 不接收缺失结束位置的旧结构,也不合成兼容范围。 ## 文本输出 diff --git a/docs/EXTENDING.md b/docs/EXTENDING.md index b1863a0..f0849d9 100644 --- a/docs/EXTENDING.md +++ b/docs/EXTENDING.md @@ -1,8 +1,8 @@ # 扩展前端、后端与代码绑定 -0.6.8 使用对称的 descriptor/registry 架构接入四种内置源语言和两个输出目标。当前核心驱动执行“选择 descriptor → 创建 parser session → parser 直接构造语言 arena AST → AST verifier → AST→窄 HIR + semantic seed → HIR/seed verifier → NameTable/FlowTable → Analyzer + normalized storage-region/numeric side table → flat MIR value/operation arena + revision-bound expression/statement/instruction attributes + lazy/memory CFG → 共享 MIR 默认优化 + 逐 pass verifier → 优化后区域化 alias/effect → CFG memory-dependence fixed point + verifier → capability/legalization → 私有 semantic plan/LIR → LIR verifier/dump → printer”,不按具体语言或目标硬编码分派。TypeScript 证明了同一扩展边界既可承载独立 token stream/arena 和 explicit export policy,也可通过 semantic profile 选择 lexical-block scope model,而无需修改 emitter 分派。新增源语言只负责产生同一 `MemoryAccess`、`NumericType` 和 `ArrayStorageFormat`、`MatrixOperationPlan`、`SparseConstructionPlan`、`SparseIndexPlan`、`SparseMutationPlan`、`SparseReshapePlan` 与 `SparseElementwisePlan` contract;matrix storage policy 同时区分稠密、CSC 系数、CSC 矩阵乘法和 CSC 标量缩放。RAW/WAR/WAW、unknown barrier 与 loop-carried 分析继续由公共 MIR 层统一完成,前端和目标后端都不得复制。当前 descriptor contract 面向同一源码树中的编译期组件,只接受 canonical name,并且不承诺跨版本 C++ 布局或动态库插件 ABI。 +0.6.9 使用对称的 descriptor/registry 架构接入四种内置源语言和两个输出目标。当前核心驱动执行“选择 descriptor → 创建 parser session → parser 直接构造语言 arena AST → AST verifier → AST→窄 HIR + semantic seed → HIR/seed verifier → NameTable/FlowTable → Analyzer + normalized storage-region/numeric side table → flat MIR value/operation arena + revision-bound expression/statement/instruction attributes + lazy/memory CFG → 共享 MIR 默认优化 + 逐 pass verifier → 优化后区域化 alias/effect → CFG memory-dependence fixed point + verifier → capability/legalization → 私有 semantic plan/LIR → LIR verifier/dump → printer”,不按具体语言或目标硬编码分派。TypeScript 证明了同一扩展边界既可承载独立 token stream/arena 和 explicit export policy,也可通过 semantic profile 选择 lexical-block scope model,而无需修改 emitter 分派。新增源语言只负责产生同一 `MemoryAccess`、`NumericType` 和 `ArrayStorageFormat`、`MatrixOperationPlan`、`SparseConstructionPlan`(含 sparse value domain/duplicate policy)、`SparseIndexPlan`、`SparseMutationPlan`、`SparseReshapePlan` 与 `SparseElementwisePlan` contract;matrix storage policy 同时区分稠密、CSC 系数、CSC 矩阵乘法和 CSC 标量缩放。RAW/WAR/WAW、unknown barrier 与 loop-carried 分析继续由公共 MIR 层统一完成,前端和目标后端都不得复制。当前 descriptor contract 面向同一源码树中的编译期组件,只接受 canonical name,并且不承诺跨版本 C++ 布局或动态库插件 ABI。 -本页记录当前可执行的 frontend API v6/backend API v6 接入方式以及尚未完成的动态插件 contract。语言 AST artifact、direct arena builder、窄 HIR v2 + frontend semantic seed、Analyzer 直写 side table、profile 驱动 `NameScopeEdges`、独立 flow/alias-effect、MIR resident instruction + ID arena、共享默认优化、call argument borrow/copy/optional-forward/normalized-region contract、按 `InstructionId` 稠密的区域化 `MemoryAccess`、当前控制结构 CFG、`SymbolId` target inventory、LIR v34 lexical `ScopePlan`、scalar-division/zero-denominator policy、Matlab array-literal/broadcast/reduction shape source 与 sparse-construction/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise plan、matrix-operation/numeric-domain/solve/condition-policy/factorization-policy/structure-policy/storage-policy plan、逐下标 selector/extent identity、目标 runtime shape 调用 ABI 与 Semantic v21→MIR v27→LIR v34 的 numeric/storage/mutation contract、目标 lowering 和纯 serialized-chunk emitter 已实际进入生产路径;静态已知 shape 的同根 N 维 selector overlap 与直接/跨调用 memory effect 已由公共 Analyzer/MIR/alias 层完成,动态 `end` 使用强类型 runtime-axis/runtime-linear contract,Matlab local-function compatible-size 使用 runtime-operands broadcast contract,shape-changing write 统一声明整个 aggregate root 的写 effect。 +本页记录当前可执行的 frontend API v6/backend API v6 接入方式以及尚未完成的动态插件 contract。语言 AST artifact、direct arena builder、窄 HIR v2 + frontend semantic seed、Analyzer 直写 side table、profile 驱动 `NameScopeEdges`、独立 flow/alias-effect、MIR resident instruction + ID arena、共享默认优化、call argument borrow/copy/optional-forward/normalized-region contract、按 `InstructionId` 稠密的区域化 `MemoryAccess`、当前控制结构 CFG、`SymbolId` target inventory、LIR v35 lexical `ScopePlan`、scalar-division/zero-denominator policy、Matlab array-literal/broadcast/reduction shape source 与 sparse-construction/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise plan、matrix-operation/numeric-domain/solve/condition-policy/factorization-policy/structure-policy/storage-policy plan、逐下标 selector/extent identity、目标 runtime shape/integer 调用 ABI 与 Semantic v22→MIR v28→LIR v35 的 numeric/storage/mutation contract、目标 lowering 和纯 serialized-chunk emitter 已实际进入生产路径;静态已知 shape 的同根 N 维 selector overlap 与直接/跨调用 memory effect 已由公共 Analyzer/MIR/alias 层完成,动态 `end` 使用强类型 runtime-axis/runtime-linear contract,Matlab local-function compatible-size 使用 runtime-operands broadcast contract,shape-changing write 统一声明整个 aggregate root 的写 effect。 `ArrayLiteralPlan` 还为 shaped-empty literal 固化不可由嵌套容器反推的 shape,JavaScript 通过 descriptor 消费该计划,C++ 通过静态 shape 参数消费对应契约;矩形 real/complex Matlab solve 必须显式选择 basic-solution-with-warning 与 rank-revealing-column-pivoted-QR policy,real/complex 方阵必须分别选择 square-continue-with-warning 与 classify-real-square/classify-complex-square policy;sparse constructor 必须携带 kind/result-shape/triplet-cardinality/reserve-hint,sparse index 必须携带 kind/input-result-shape/source-result-storage,sparse mutation 还必须携带 assignment/deletion identity、四类 shape、replacement storage、scalar-expansion、duplicate-write 与 zero-write policy,并在目标层独立验证;sparse square solve 还必须同时携带 CSC coefficient storage、sparse row-pivoted LU 与 sparse-real-square structure policy。目标 representation planner 必须把 helper 所需的所有 shape 按调用顺序固化到 `runtime_shape_arguments`,目标 verifier 与 deterministic dump 必须验证该 ABI;renderer 只能序列化 token 与 shape 实参,不得从 semantic policy、helper 名称或 operand shape 恢复数值条件、存储行为或调用签名。一般 NDArray/typed-array ownership、跨语言动态 shape 数据流、pointer/view association 与 region 组合、完整官方 grammar 及独立 target AST 仍不是已经完成的扩展接口。权威边界见 [商业级编译器管线方案](COMPILER_PIPELINE.md)。 diff --git a/docs/LANGUAGE_SUPPORT.md b/docs/LANGUAGE_SUPPORT.md index 4da715a..fcc2563 100644 --- a/docs/LANGUAGE_SUPPORT.md +++ b/docs/LANGUAGE_SUPPORT.md @@ -4,9 +4,9 @@ TypeScript frontend 已注册,当前 manifest 范围为 1.0—6.0,并有 Node.js 24 source/生成 JavaScript/生成 C++17/声明式 oracle 四路差分;这只声明下表中已验证的 typed/lexical-block/canonical-for 子集,不表示完整 TypeScript 6 grammar 兼容。 -本表只描述语言行为覆盖,不以内部架构工作代替语言兼容声明。四个 statement parser 已直接构造编译期互不兼容的语言专属 PMR arena AST,不再经过共享 syntax tree;MIR 以强类型 ID 连接 flat expression/operation arena,每个节点绑定 resident instruction,非结构事实进入 revision-bound attribute table,直接 memory operation 另有按 `InstructionId` 稠密的 storage/root/region/mode 访问行。conditional/短路/comparison chain 与 TypeScript canonical `for` 已由 MIR CFG 固定求值/控制边,变量与 writable section call 使用显式 load/store/copy/writeback。TypeScript lexical scope 由 `NameScopeEdges`、Analyzer block state、`SymbolId` target identity 和 LIR v34 `ScopePlan` 贯通,不依赖 emitter 猜测 brace binding;explicit export policy 同样通过 semantic side table、MIR function 与 JavaScript LIR ABI 传递。 +本表只描述语言行为覆盖,不以内部架构工作代替语言兼容声明。四个 statement parser 已直接构造编译期互不兼容的语言专属 PMR arena AST,不再经过共享 syntax tree;MIR 以强类型 ID 连接 flat expression/operation arena,每个节点绑定 resident instruction,非结构事实进入 revision-bound attribute table,直接 memory operation 另有按 `InstructionId` 稠密的 storage/root/region/mode 访问行。conditional/短路/comparison chain 与 TypeScript canonical `for` 已由 MIR CFG 固定求值/控制边,变量与 writable section call 使用显式 load/store/copy/writeback。TypeScript lexical scope 由 `NameScopeEdges`、Analyzer block state、`SymbolId` target identity 和 LIR v35 `ScopePlan` 贯通,不依赖 emitter 猜测 brace binding;explicit export policy 同样通过 semantic side table、MIR function 与 JavaScript LIR ABI 传递。 -后续生产路径先经过共享保守 MIR 默认优化,再基于最终 revision 计算独立 alias/effect,将跨函数参数读写按 actual region 实例化;`MemoryDependenceTable` v1 随后在函数 CFG 上建立 region-refined RAW/WAR/WAW、unknown barrier 与 loop-carried facts,最后才进入目标 semantic/rendered LIR→纯 emitter。整数常量折叠只覆盖 `int64` 与 ECMAScript safe-integer 的共同精确域,内存依赖层也只提供后续优化所需证明,不扩大任何源语言 grammar 或数值兼容声明。静态一般 rank 的声明、RESHAPE、直接 section、Matlab static/runtime-operands compatible-size broadcast、matrix solve/condition/factorization/structure/storage policy、sparse-construction/sparse-index/sparse-mutation/sparse-reshape 与 sparse-multiply/sparse-scale storage plan、`SparseElementwisePlan`、逐下标 selector/runtime extent、规范 `0×0` empty 与 shaped-empty target plan,以及 Semantic v21→MIR v27→LIR v34 的 numeric class/complexity、dense/CSC array storage、overwrite/resize/grow/erase mutation contract、borrow/copy/writeback、目标 runtime shape 调用 ABI 和同一已知 storage root 上的 element/连续/步长/N 维矩形 selector overlap 证明已完成;尚未覆盖的官方 grammar、跨语言动态 shape 与一般 NDArray 表示、其余 sparse 语义、跨一般 pointer/view 的 storage association/region composition、完整 MemorySSA 与跨函数对象语义仍见 [商业级编译器管线方案](COMPILER_PIPELINE.md) 和 [TODO](../TODO.md)。 +后续生产路径先经过共享保守 MIR 默认优化,再基于最终 revision 计算独立 alias/effect,将跨函数参数读写按 actual region 实例化;`MemoryDependenceTable` v1 随后在函数 CFG 上建立 region-refined RAW/WAR/WAW、unknown barrier 与 loop-carried facts,最后才进入目标 semantic/rendered LIR→纯 emitter。整数常量折叠只覆盖 `int64` 与 ECMAScript safe-integer 的共同精确域,内存依赖层也只提供后续优化所需证明,不扩大任何源语言 grammar 或数值兼容声明。静态一般 rank 的声明、RESHAPE、直接 section、Matlab static/runtime-operands compatible-size broadcast、matrix solve/condition/factorization/structure/storage policy、带值域与重复项策略的 sparse-construction/sparse-index/sparse-mutation/sparse-reshape 与 sparse-multiply/sparse-scale storage plan、`SparseElementwisePlan`、逐下标 selector/runtime extent、规范 `0×0` empty 与 shaped-empty target plan,以及 Semantic v22→MIR v28→LIR v35 的 numeric class/complexity、dense/CSC array storage、overwrite/resize/grow/erase mutation contract、borrow/copy/writeback、目标 runtime shape/integer 调用 ABI 和同一已知 storage root 上的 element/连续/步长/N 维矩形 selector overlap 证明已完成;尚未覆盖的官方 grammar、跨语言动态 shape 与一般 NDArray 表示、其余 sparse 语义、跨一般 pointer/view 的 storage association/region composition、完整 MemorySSA 与跨函数对象语义仍见 [商业级编译器管线方案](COMPILER_PIPELINE.md) 和 [TODO](../TODO.md)。 表中的“支持”表示当前子集已经进入语义/后端测试;可执行能力还应进入差分 corpus。只在某个目标可表示的结构必须由另一个目标的 capability validator 明确拒绝,不能静默改变语义。 @@ -26,7 +26,7 @@ TypeScript frontend 已注册,当前 manifest 范围为 1.0—6.0,并有 Nod | 函数 | 基础 `def`、参数、`return`;不可变标量默认值、keyword actual、`/` positional-only、裸 `*` keyword-only;前向调用;静态可表示返回的直接/互递归 | 基础文件级 local `function`;前向调用;单/多输出及跨函数转发;`[a,b]=f(...)` 单次调用;标量上下文选择首输出 | 基础 internal/external `FUNCTION`/`SUBROUTINE`、类型前缀、`RESULT`、`CALL`、`RETURN`、`RECURSIVE` 与 `INTENT(IN/OUT/INOUT)`;默认 intent;标量、整数组、数组元素及一/二维 section actual 写回;基础 keyword association;标量及一/二维数组 `OPTIONAL` 的 IN/OUT/INOUT、`PRESENT`、缺省调用与 optional 透传;前向/递归调用 | module-level `function`、参数/返回 annotation 擦除、不可变标量 default、值 `return`、前向调用;`export function` 保留显式 ESM export;nested function 失败关闭 | | 常用数学 intrinsic | 映射到目标数学库 | 映射到目标数学库 | 映射到目标数学库 | 尚未声明全局 source intrinsic;不把 `Math.*` 错认作共享拼写 | | 容器字面量 | 任意深度矩形嵌套 list | 逗号/空格列、分号分行矩阵;`reshape` 接受任意已知 rank 源和非空维度列表 | `[...]`、旧式 `(/.../)`;任意已知 rank 源/目标 `RESHAPE` | homogeneous array literal 与 `number[]`/`string[]`/`boolean[]` annotation | -| 稀疏矩阵 | 尚未支持 | R2024 全部 `sparse` 调用形式在静态 real rank-2 contract(包含零 extent)内的 canonical CSC 构造,含 zero/empty、推断/显式 shape、scalar expansion、重复项累加与 `nzmax`;scalar/linear/submatrix indexing;linear/subscript assignment、dense/sparse RHS、scalar expansion、重复下标 last-write-wins、zero erase、静态 growth 与合法 null deletion;selector 支持 colon/slice、保序/重复/空 numeric 与 logical;列主序 reshape;`full`、`issparse`、`nnz`、real CSC 普通/共轭转置;含 `0×0` 系数与 shaped-empty operand/result 的实数方阵 CSC 左右除;finite-real rank-2 CSC×CSC、CSC×dense、dense×CSC 矩阵乘法,以及双向 CSC×scalar 缩放;矩阵乘法按 sparse×sparse→CSC、混合乘法→dense,标量乘法保持 canonical CSC;compatible-size CSC `.*` scalar、dense 或 CSC 均直接返回 canonical CSC | 尚未支持 | 尚未支持 | +| 稀疏矩阵 | 尚未支持 | R2024 全部 `sparse` 调用形式对静态 real/logical rank-2 值输入的 canonical CSC 构造,含 zero/empty、推断/显式 shape、scalar expansion、numeric 重复项累加、logical 重复项 `any` 聚合与 `nzmax`;scalar/linear/submatrix indexing;linear/subscript assignment、dense/sparse RHS、scalar expansion、重复下标 last-write-wins、zero erase、静态 growth 与合法 null deletion;selector 支持 colon/slice、保序/重复/空 numeric 与 logical;列主序 reshape;保持 real/logical class 的 `full`、`issparse`、`nnz` 与 CSC 普通/共轭转置;含 `0×0` 系数与 shaped-empty operand/result 的实数方阵 CSC 左右除;finite-real rank-2 CSC×CSC、CSC×dense、dense×CSC 矩阵乘法,以及双向 CSC×scalar 缩放;矩阵乘法按 sparse×sparse→CSC、混合乘法→dense,标量乘法保持 canonical CSC;compatible-size CSC `.*` scalar、dense 或 CSC 均直接返回 canonical CSC | 尚未支持 | 尚未支持 | | 标量索引读取/写入 | 0-based,支持负下标与任意深度链式索引 | 1-based,列主序线性/逐维索引;静态或运行时 extent 的上下文 `end`;保序/重复/空 numeric selector;线性/逐维 logical selector 读写 | 1-based;引用 rank 必须匹配声明 rank | 0-based array read/write;const container element mutation 可用;当前只接受可证明的整数常量 `number` index,负下标不作 Python 归一化 | | section/slice 读取 | `start:stop:step`,缺省 bound、负 step、exclusive stop/clamp;直接多 selector 支持任意静态 rank | `:`, `start:stop`, `start:step:stop`;静态或运行时 extent 的上下文 `end` 可参与 bound;行/列/block/线性选取及任意静态 rank 直接 selector | `lower:upper:stride`;任意静态 rank 直接 section、缺省 bound、负 stride | 尚未支持 | | section/slice 写入 | 一维 list 普通切片可变长度替换;extended slice 等长替换;固定 shape 多 selector 写入 | 行、列、block、列主序线性 colon 及任意静态 rank 直接 section 赋值;支持标量扩展 | 任意静态 rank 直接 array section 赋值;支持标量扩展 | 尚未支持 | @@ -68,13 +68,13 @@ Fortran `SELECT CASE` selector 只求值一次。CASE bound 当前接受 integer Matlab `switch` selector 同样只求值一次,当前支持 scalar numeric、logical 和 character case、`otherwise` 及按源码顺序首个匹配分支,不产生 fallthrough。character case 使用精确文本相等,不采用 Fortran 的补空格规则。cell-array case、string object 和一般对象 equality 尚未支持。Matlab → JavaScript 的完整缺口和验收顺序见 [专项产品计划](MATLAB_TO_JAVASCRIPT.md)。 -Matlab 算术和逻辑在 token、AST/HIR/MIR 与目标 LIR 中区分矩阵运算、逐元素运算与短路求值。`NumericType` 又将 logical/signed-integer/binary64 class 与 real/complex complexity 正交保存;expression、element、statement、parameter、return 和 tuple side table 均由 Semantic v21、MIR v27 与双目标 LIR v34 验证。`2i`/`3j` 和可遮蔽 builtin `i`/`j` 产生 binary64 complex;支持 complex scalar `+`/`-`/`*`/`/`/`\`/`^`、一元正负、标量 `complex`/`conj`/`real`/`imag`/`abs`,以及 complex 数组 compatible-size 逐元素运算、索引/写入/reshape、二维矩阵乘法、稠密方阵与超定/欠定矩形左右除和 safe-integer 矩阵幂。无类型 local-function 参数保留 unknown complexity,由 JavaScript object ABI 或 C++ `std::complex` 目标私有 helper 动态分派 real/complex;real-only 代码不会携带 complex runtime。complex comparison/logical/reduction 仍以 `MPF2053` 失败关闭。 +Matlab 算术和逻辑在 token、AST/HIR/MIR 与目标 LIR 中区分矩阵运算、逐元素运算与短路求值。`NumericType` 又将 logical/signed-integer/binary64 class 与 real/complex complexity 正交保存;expression、element、statement、parameter、return 和 tuple side table 均由 Semantic v22、MIR v28 与双目标 LIR v35 验证。`2i`/`3j` 和可遮蔽 builtin `i`/`j` 产生 binary64 complex;支持 complex scalar `+`/`-`/`*`/`/`/`\`/`^`、一元正负、标量 `complex`/`conj`/`real`/`imag`/`abs`,以及 complex 数组 compatible-size 逐元素运算、索引/写入/reshape、二维矩阵乘法、稠密方阵与超定/欠定矩形左右除和 safe-integer 矩阵幂。无类型 local-function 参数保留 unknown complexity,由 JavaScript object ABI 或 C++ `std::complex` 目标私有 helper 动态分派 real/complex;real-only 代码不会携带 complex runtime。complex comparison/logical/reduction 仍以 `MPF2053` 失败关闭。 `LogicalEvaluation` 明确保存 eager-elementwise、boolean-short-circuit 或 operand-short-circuit,目标 renderer 不从符号重新推断;`&`/`|` 与关系运算之间、`&&`/`||` 之间的 precedence 按 Matlab 文档实现。当前已验证二维 real/complex 矩阵 `*`,静态稠密实数/复数方阵、静态 real rank-2 CSC 方阵(包含零 extent)及超定/欠定稠密矩形矩阵的 `\`/`/` 求解与 safe-integer 方阵 `^`,以及静态 shape 或 local-function runtime rank/extent 下 compatible-size 隐式扩展;稠密方阵求解按 real structure policy 选择直接对角、三角替换、带相邻行主元三对角 LU、Cholesky 或部分主元 dense LU,并以结构专属正/转置求解的迭代 1-范数估计区分正常、近奇异和精确奇异条件。矩形 real/complex 求解采用 rank-aware 带列主元 Householder QR,返回 pivoted 基本最小二乘解,多 RHS 共用分解,数值秩亏时继续计算并稳定警告;复数右除按共轭转置恒等式复用同一求解器。缺失的尾随维度按 singleton 处理,vector 与高 rank operand 组合时按 Matlab row vector 规范化;runtime-operands plan 在两目标独立验证 rectangularity 与 extent 兼容性。`~`/`&`/`|` 对 real numeric/logical scalar 或数组产生 logical,`&&`/`||` 对 scalar 短路;条件数组仅在非空且所有元素非零时为真。共轭 `'` 与非共轭 `.'` 保留独立身份并支持 real/complex vector/rank-2 operand;上下文 `end` 在静态 shape 上直接折叠,在动态 extent 上通过强类型 axis/linear extent plan 延迟到目标 runtime。广义 selector、mutation 和 null-assignment 继续遵循列主序及既有 shape contract。一般 NDArray/typed-array 值语义与跨函数不可结构恢复 shape、动态 sparse 操作、非整数矩阵幂和其余 numeric class 尚未支持,并以 `MPF2046`—`MPF2054` 或稳定 runtime 错误失败关闭。 `all`/`any` 对 numeric/logical scalar、vector、matrix 和静态一般 N 维数组产生 logical。单参数调用按首个 extent 不为 1 的维度归约;`dim`/`vecdim` 必须是可验证的正整数常量且 `vecdim` 不得重复,超过 rank 的维度保持不变;`'all'` 产生 scalar。归约维的 extent 在结果中置 1,`0×0` 默认归约按 Matlab identity 分别得到 true/false;JavaScript shape descriptor 与 C++ 静态 shape 参数都保留 `0×N`/`N×0` 等不可从空容器恢复的差异。local-function 未知 rank 当前只支持 `'all'`,字符数组与动态维度等待统一 NDArray/numeric-class contract,并以 `MPF2052` 失败关闭。 -实数稠密方阵左除和右除按固定优先级选择 exact-zero diagonal、upper/lower-triangular、full tridiagonal、exact-symmetric positive-definite 或 dense fallback。三对角路径使用相邻行部分主元紧凑 LU 和专用正/转置求解;非三对角对称候选只有完整 Cholesky 成功后才使用正定路径,否则回退部分主元 dense LU。R2024 的五类 `sparse` 调用形式在静态 real rank-2 contract(包含零 extent)内由 `SparseConstructionPlan` 固定 constructor identity、result shape、triplet cardinality 与 reserve hint;双目标 runtime 直接构造 canonical CSC,支持 triplet scalar expansion、显式空输入、重复项累加/抵消与普通/共轭转置。CSC 索引读取由 `SparseIndexPlan` 区分 linear/subscript scalar 与 storage-preserving linear/submatrix selection:一个 selector 接受 scalar、colon、slice、保序/重复/空 numeric 或 logical,两个 selector 按行列 Cartesian product 选择;scalar 返回 dense scalar,其余返回 canonical CSC。`A(P)` 在 A 与 P 都是 vector 时继承 A 的方向,numeric matrix P 保持自身 shape,logical matrix 与 `A(:)` 返回 column,`A(I,J)` 为 `numel(I)×numel(J)`;`A(:)` 走 O(nnz) CSC 快路,其他选择也不物化 dense source。`SparseReshapePlan` 另行固定 size-vector 或维度列表形式、至多一个 `[]` 推断轴、输入/请求/result shape 与 CSC storage;请求 rank 大于 2 时按 Matlab sparse 规则保留首维并将其余维度乘入第二维。两个目标按原 CSC 遍历顺序以 O(nnz + result-columns) 重映射线性位置,不排序也不物化 dense source。静态 real rank-2 CSC 系数矩阵另由 storage policy 授权:canonical CSC 三对角结构走 O(nnz) 紧凑分解,其他非空结构转为有序 sparse rows 后执行部分行主元 LU 与稀疏 fill-in;`0×0` 系数直接按目标 LIR 的三组 runtime shape 实参产生 `0×n` 或 `m×0` shaped-empty 结果。右除通过 CSC 转置复用同一求解,结果 storage 对左除跟随 RHS、对右除跟随 LHS;两类路径都使用与自身正/转置求解器匹配的迭代 1-范数倒条件估计。`MatrixStoragePolicy::sparse_csc_multiply` 另行授权 finite-real rank-2 乘法:CSC×CSC 以每结果列的 scatter accumulator 直接产生 canonical CSC,仅排序触及 row 并丢弃 exact-zero cancellation;两类 mixed product 只遍历 CSC 非零项并直接累计 dense result。`MatrixStoragePolicy::sparse_csc_scale` 授权 CSC×scalar 与 scalar×CSC:非零 finite real/logical factor 只遍历 nnz,零因子直接产生同 shape 空 CSC,underflow exact-zero 不进入存储。`SparseElementwisePlan` 与矩阵计划独立,显式固定 CSC `.*` scalar、dense 或 CSC 的左右 storage/shape、compatible-size broadcast axis 和 preserve-sparse result;目标 runtime 只扫描非零项或合并 CSC 列,支持 singleton row/column expansion,并删除 exact-zero result。`SparseMutationPlan` 另行授权线性/双下标 assignment 与合法 linear/axis deletion:支持 scalar expansion、dense/sparse RHS、重复 selector 的列主序 last-write-wins、exact-zero entry erase、静态线性/逐维扩容和 self-alias;两个目标用 O(nnz + k log k) 排序/折叠/归并,完成所有验证后才事务式替换 CSC。以上已支持路径均保留静态零 extent;超过两个 selector、complex/sparse selector、N-D linear index result、动态或 complex sparse source,以及 sparse logical/幂和矩形 solve 仍失败关闭。实数路径不声明容差结构识别;R2024 对 full symmetric-indefinite 的 dense LU 行为已由 fallback 覆盖。 +实数稠密方阵左除和右除按固定优先级选择 exact-zero diagonal、upper/lower-triangular、full tridiagonal、exact-symmetric positive-definite 或 dense fallback。三对角路径使用相邻行部分主元紧凑 LU 和专用正/转置求解;非三对角对称候选只有完整 Cholesky 成功后才使用正定路径,否则回退部分主元 dense LU。R2024 的五类 `sparse` 调用形式对静态 real/logical rank-2 值输入由 `SparseConstructionPlan` 固定 constructor identity、result shape、triplet cardinality 与 reserve hint;`sparse(m,n)` 没有值输入并保持 Matlab double 结果;双目标 runtime 直接构造 canonical CSC,支持 triplet scalar expansion、显式空输入、numeric 重复项求和/抵消、logical 重复项按 `any` 聚合,并保持普通/共轭转置的值域。selection、reshape、mutation 与 `full` 同样保留 logical/real class。CSC 索引读取由 `SparseIndexPlan` 区分 linear/subscript scalar 与 storage-preserving linear/submatrix selection:一个 selector 接受 scalar、colon、slice、保序/重复/空 numeric 或 logical,两个 selector 按行列 Cartesian product 选择;scalar 返回 dense scalar,其余返回 canonical CSC。`A(P)` 在 A 与 P 都是 vector 时继承 A 的方向,numeric matrix P 保持自身 shape,logical matrix 与 `A(:)` 返回 column,`A(I,J)` 为 `numel(I)×numel(J)`;`A(:)` 走 O(nnz) CSC 快路,其他选择也不物化 dense source。`SparseReshapePlan` 另行固定 size-vector 或维度列表形式、至多一个 `[]` 推断轴、输入/请求/result shape 与 CSC storage;请求 rank 大于 2 时按 Matlab sparse 规则保留首维并将其余维度乘入第二维。两个目标按原 CSC 遍历顺序以 O(nnz + result-columns) 重映射线性位置,不排序也不物化 dense source。静态 real rank-2 CSC 系数矩阵另由 storage policy 授权:canonical CSC 三对角结构走 O(nnz) 紧凑分解,其他非空结构转为有序 sparse rows 后执行部分行主元 LU 与稀疏 fill-in;`0×0` 系数直接按目标 LIR 的三组 runtime shape 实参产生 `0×n` 或 `m×0` shaped-empty 结果。右除通过 CSC 转置复用同一求解,结果 storage 对左除跟随 RHS、对右除跟随 LHS;两类路径都使用与自身正/转置求解器匹配的迭代 1-范数倒条件估计。`MatrixStoragePolicy::sparse_csc_multiply` 另行授权 finite-real rank-2 乘法:CSC×CSC 以每结果列的 scatter accumulator 直接产生 canonical CSC,仅排序触及 row 并丢弃 exact-zero cancellation;两类 mixed product 只遍历 CSC 非零项并直接累计 dense result。`MatrixStoragePolicy::sparse_csc_scale` 授权 CSC×scalar 与 scalar×CSC:非零 finite real/logical factor 只遍历 nnz,零因子直接产生同 shape 空 CSC,underflow exact-zero 不进入存储。`SparseElementwisePlan` 与矩阵计划独立,显式固定 CSC `.*` scalar、dense 或 CSC 的左右 storage/shape、compatible-size broadcast axis 和 preserve-sparse result;目标 runtime 只扫描非零项或合并 CSC 列,支持 singleton row/column expansion,并删除 exact-zero result。`SparseMutationPlan` 另行授权线性/双下标 assignment 与合法 linear/axis deletion:支持 scalar expansion、dense/sparse RHS、重复 selector 的列主序 last-write-wins、exact-zero entry erase、静态线性/逐维扩容和 self-alias;两个目标用 O(nnz + k log k) 排序/折叠/归并,完成所有验证后才事务式替换 CSC。以上已支持路径均保留静态零 extent;超过两个 selector、complex/sparse selector、N-D linear index result、动态或 complex sparse source,以及 sparse logical 算术/逐元素逻辑/归约、幂和矩形 solve 仍失败关闭。实数路径不声明容差结构识别;R2024 对 full symmetric-indefinite 的 dense LU 行为已由 fallback 覆盖。 复数方阵按 exact Hermitian 检测选择 complex Cholesky;非正定或非 Hermitian 候选回退按模选主元 dense LU。方阵左除支持多列 RHS,右除通过共轭转置系统求解;两条路径都计算 1-范数倒条件估计,并在 singular/nearly-singular 时警告后继续。复数方阵幂使用平方求幂,负整数幂先通过同一方阵 solver 求逆。复数矩形左/右除使用 rank-revealing CPQR,超定/欠定与多 RHS 共享相同 typed factorization contract,秩亏返回 pivoted basic solution 并警告。JavaScript object runtime 与 C++ `std::complex` runtime 独立实现并按模块裁剪;complex sparse、容差 Hermitian 分类和非整数矩阵幂不在当前能力内。 @@ -99,6 +99,6 @@ Matlab 算术和逻辑在 token、AST/HIR/MIR 与目标 LIR 中区分矩阵运 | 自动验证 | Node.js syntax + execution | 平台 C++17 编译器 compile + execution | | 名称安全 | JS 保留字确定性改写 | C++ 保留字改写并隔离在 namespace | -C++17 后端使用函数模板保持基础参数类型,并根据语义分析结果生成标量和任意 rank 递归 `std::vector` 声明;Python source call 在 Analyzer 后已成为完整位置实参序列。普通 Fortran IN 参数的 `const T&`、OUT/INOUT 的 `T&`,以及 optional formal 的具体 `mpf_runtime::optional_argument` 已作为 `cpp` LIR v34 参数 ABI/访问计划固化。JavaScript 的 script/ESM、explicit export、value/reference-box ABI、一般 N 维默认数组初始化与作用域声明顺序同样驻留于 JavaScript LIR v34;两者都以 `SymbolId` inventory 和 statement/body/alternative `ScopePlan` 固化 lexical binding/declaration。module/translation-unit layout、numeric class/complexity、强类型 comparison、scalar-division runtime policy、Matlab array-literal/broadcast/sparse-construction/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise source、array/matrix operation/solve kind/condition/factorization policy、目标 runtime shape 调用 ABI、逐下标 selector identity/extent/mutation plan、custom call、first-result、N-D section、range/for/loop-else/return 均由目标 plan 固化。逐实参 plan 同时保存 optional-forward/box/copy ownership 与写回形式,JavaScript writable call 选择 arrow IIFE,C++ section copy-in/out 与 comparison evaluation 选择 reference lambda,调用或操作数只求值一次并按计划保存。optional runtime 同时保存 absent、外部引用或临时 owned value;section actual 由 JavaScript selector-aware N 维 runtime 或 C++17 typed copy-out 回写。共享 Analyzer/MIR/alias 层在进入目标前完成静态 N 维 region 证明,两个后端不重复解释 selector。Analyzer 以 `MPF2038`—`MPF2041` 拒绝不可定义、未决/重叠 alias、shape/type 或 association 不匹配,以 `MPF2044`/`MPF2045` 约束 comparison 可保持边界,并以 `MPF2046`—`MPF2054` 约束 Matlab 数组、转置、索引、mutation、logical/reduction、complex 与 sparse 边界。跨一般 pointer/view 的区域证明、可跨函数携带零 extent 的动态对象/NDArray 模型以及完整源语言对象语义尚未支持。 +C++17 后端使用函数模板保持基础参数类型,并根据语义分析结果生成标量和任意 rank 递归 `std::vector` 声明;Python source call 在 Analyzer 后已成为完整位置实参序列。普通 Fortran IN 参数的 `const T&`、OUT/INOUT 的 `T&`,以及 optional formal 的具体 `mpf_runtime::optional_argument` 已作为 `cpp` LIR v35 参数 ABI/访问计划固化。JavaScript 的 script/ESM、explicit export、value/reference-box ABI、一般 N 维默认数组初始化与作用域声明顺序同样驻留于 JavaScript LIR v35;两者都以 `SymbolId` inventory 和 statement/body/alternative `ScopePlan` 固化 lexical binding/declaration。module/translation-unit layout、numeric class/complexity、强类型 comparison、scalar-division runtime policy、Matlab array-literal/broadcast/sparse-construction value-domain/duplicate-policy/sparse-index/sparse-mutation/sparse-reshape/sparse-elementwise source、array/matrix operation/solve kind/condition/factorization policy、目标 runtime shape/integer 调用 ABI、逐下标 selector identity/extent/mutation plan、custom call、first-result、N-D section、range/for/loop-else/return 均由目标 plan 固化。逐实参 plan 同时保存 optional-forward/box/copy ownership 与写回形式,JavaScript writable call 选择 arrow IIFE,C++ section copy-in/out 与 comparison evaluation 选择 reference lambda,调用或操作数只求值一次并按计划保存。optional runtime 同时保存 absent、外部引用或临时 owned value;section actual 由 JavaScript selector-aware N 维 runtime 或 C++17 typed copy-out 回写。共享 Analyzer/MIR/alias 层在进入目标前完成静态 N 维 region 证明,两个后端不重复解释 selector。Analyzer 以 `MPF2038`—`MPF2041` 拒绝不可定义、未决/重叠 alias、shape/type 或 association 不匹配,以 `MPF2044`/`MPF2045` 约束 comparison 可保持边界,并以 `MPF2046`—`MPF2054` 约束 Matlab 数组、转置、索引、mutation、logical/reduction、complex 与 sparse 边界。跨一般 pointer/view 的区域证明、可跨函数携带零 extent 的动态对象/NDArray 模型以及完整源语言对象语义尚未支持。 当前 declarative corpus 在同一 differential case 中直接比较 CPython 3.14 或 gfortran 严格 `-std=f2018` reference mode、Node.js、生成 C++17 与 oracle;`MPF_FORTRAN_REFERENCE_STANDARD` 允许工具链支持后切换到 `f2023`。这个外部编译器模式只描述当前 corpus 的 reference 执行环境,不降低 MPF frontend 的 Fortran 2023 版本化目标。Matlab case 当前直接比较 Node.js、生成 C++17 与 oracle;源程序执行门禁将在 CI 提供可授权的 Matlab runner 或明确选定 Octave 兼容策略后加入。 diff --git a/docs/MATLAB_TO_JAVASCRIPT.md b/docs/MATLAB_TO_JAVASCRIPT.md index 73665b2..fe7fb6e 100644 --- a/docs/MATLAB_TO_JAVASCRIPT.md +++ b/docs/MATLAB_TO_JAVASCRIPT.md @@ -14,11 +14,11 @@ renderer、source map 和差分框架能够工作,但还不能安全承载一 | 维度 | 当前状态 | 商用阻断项 | |---|---|---| | 源码与语句 | logical statement、注释、续行、脚本、local function、分支、循环和标量 `switch` | 完整 command syntax、`try/catch`、workspace 声明、包/类、产生式级 recovery | -| 表达式 | real/complex 标量与数组算术、imaginary literal、可遮蔽 `i`/`j`、`complex`/`conj`/`real`/`imag`/`abs`、complex 共轭/普通转置;real 关系比较、`~`/`&`/`|` compatible-size N 维逐元素逻辑、scalar `&&`/`||` 与 condition-context `&`/`|` 短路、数组 condition 全元素 truthiness、`all`/`any` 默认/显式/全维逻辑归约、二维 real/complex 矩阵乘法、静态 finite-real rank-2 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放及 sparse/dense/scalar compatible-size `.*`、静态稠密实数方阵的 diagonal/upper/lower/pivoted-tridiagonal/symmetric-positive-definite/dense 结构感知求解、稠密 complex 方阵的 Hermitian-positive-definite Cholesky/dense LU 求解、rank-aware real/complex 矩形基本最小二乘求解、静态 real rank-2 CSC 方阵求解(含 `0×0` 系数与 shaped-empty 两侧操作数/结果)及 real/complex safe-integer 方阵幂、R2024 全部 `sparse` 调用形式在静态 real rank-2 contract(包含零 extent)内的构造、普通/共轭转置、scalar/linear/submatrix indexing、indexed assignment/growth/deletion 与列主序 reshape(size vector、维度列表、单个 `[]` 推断及 N 维请求折叠)、`full`/`issparse`/`nnz`、调用、向量/矩阵字面量、静态及运行时 extent 的上下文 `end`、colon/index/section、保序/重复/空 numeric selector、线性及逐维 logical selector | sparse logical/power/rectangular/complex 与动态 sparse shape,complex comparison/logical/reduction,病态方阵特定解选择的精确 Matlab 对齐和非整数矩阵幂 | -| 数据模型 | `NumericClass` 的 logical/signed-integer/binary64 与独立 real/complex complexity、boolean、字符文本的当前子集、矩形嵌套数组、静态 real rank-2 canonical CSC(包含零 extent)与类型化 `SparseConstructionPlan`/`SparseIndexPlan`/`SparseMutationPlan`/`SparseReshapePlan`/`SparseElementwisePlan`/`sparse_csc_multiply`/`sparse_csc_scale` storage policy、带不可枚举 shape descriptor 的 JavaScript 零 extent 数组;JavaScript complex object 与 C++ `std::complex` ABI | single/其余整数 class、一般动态 sparse shape、string、cell、struct、table、datetime、对象 | -| 数组语义 | 1-based、列主序、规范 `0×0` empty、静态零 extent reshape/transpose/broadcast/section/growth、静态 N 维 reshape/section,以及静态 shape 或 local-function runtime rank/extent 的 compatible-size 隐式扩展;complex 数组逐元素/broadcast、索引/写入/reshape/转置;二维 real/complex 矩阵乘法、rank-aware real/complex rectangular solve、real/complex 稠密方阵 solve 与 integer power、广义 selector 读写、vector/matrix/N 维多轴扩容,以及 `all`/`any` 的 N 维/零 extent 归约、符合 null-assignment 规则的单轴删除;稀疏子集支持全部 R2024 constructor form、scalar expansion、重复项累加/抵消、real transpose、scalar/linear/submatrix indexing、indexed assignment、重复下标 last-write-wins、zero erase、静态扩容与合法 null deletion,并按 Matlab 左除 RHS/右除 LHS、sparse×sparse/mixed matrix product、双向 sparse/scalar 缩放及 compatible-size sparse `.*` scalar/dense/sparse 规则保持零 extent 与结果 storage | 可跨函数携带不可结构恢复 shape 的统一动态 NDArray/typed-array ABI、其余 sparse 操作与非整数幂、完整动态 bounds、值语义和 alias 契约 | +| 表达式 | real/complex 标量与数组算术、imaginary literal、可遮蔽 `i`/`j`、`complex`/`conj`/`real`/`imag`/`abs`、complex 共轭/普通转置;real 关系比较、`~`/`&`/`|` compatible-size N 维逐元素逻辑、scalar `&&`/`||` 与 condition-context `&`/`|` 短路、数组 condition 全元素 truthiness、`all`/`any` 默认/显式/全维逻辑归约、二维 real/complex 矩阵乘法、静态 finite-real rank-2 sparse×sparse/sparse×dense/dense×sparse 矩阵乘法、双向 sparse/scalar 缩放及 sparse/dense/scalar compatible-size `.*`、静态稠密实数方阵的 diagonal/upper/lower/pivoted-tridiagonal/symmetric-positive-definite/dense 结构感知求解、稠密 complex 方阵的 Hermitian-positive-definite Cholesky/dense LU 求解、rank-aware real/complex 矩形基本最小二乘求解、静态 real rank-2 CSC 方阵求解(含 `0×0` 系数与 shaped-empty 两侧操作数/结果)及 real/complex safe-integer 方阵幂、R2024 全部 `sparse` 调用形式对静态 real/logical rank-2 值输入(包含零 extent)的构造、普通/共轭转置、scalar/linear/submatrix indexing、indexed assignment/growth/deletion 与列主序 reshape(size vector、维度列表、单个 `[]` 推断及 N 维请求折叠)、`full`/`issparse`/`nnz`、调用、向量/矩阵字面量、静态及运行时 extent 的上下文 `end`、colon/index/section、保序/重复/空 numeric selector、线性及逐维 logical selector | sparse logical 算术/逐元素逻辑/归约、power/rectangular/complex 与动态 sparse shape,complex comparison/logical/reduction,病态方阵特定解选择的精确 Matlab 对齐和非整数矩阵幂 | +| 数据模型 | `NumericClass` 的 logical/signed-integer/binary64 与独立 real/complex complexity、boolean、字符文本的当前子集、矩形嵌套数组、静态 real/logical rank-2 canonical CSC(包含零 extent)与类型化 `SparseConstructionPlan`/`SparseIndexPlan`/`SparseMutationPlan`/`SparseReshapePlan`/`SparseElementwisePlan`/`sparse_csc_multiply`/`sparse_csc_scale` storage policy、带不可枚举 shape descriptor 的 JavaScript 零 extent 数组;JavaScript complex object 与 C++ `std::complex` ABI | single/其余整数 class、一般动态 sparse shape、string、cell、struct、table、datetime、对象 | +| 数组语义 | 1-based、列主序、规范 `0×0` empty、静态零 extent reshape/transpose/broadcast/section/growth、静态 N 维 reshape/section,以及静态 shape 或 local-function runtime rank/extent 的 compatible-size 隐式扩展;complex 数组逐元素/broadcast、索引/写入/reshape/转置;二维 real/complex 矩阵乘法、rank-aware real/complex rectangular solve、real/complex 稠密方阵 solve 与 integer power、广义 selector 读写、vector/matrix/N 维多轴扩容,以及 `all`/`any` 的 N 维/零 extent 归约、符合 null-assignment 规则的单轴删除;稀疏子集支持全部 R2024 constructor form、scalar expansion、数值重复项求和/抵消、logical 重复项 `any` 聚合、保持值域的 transpose/index/mutation/reshape/`full`、scalar/linear/submatrix indexing、indexed assignment、重复下标 last-write-wins、zero erase、静态扩容与合法 null deletion,并按 Matlab 左除 RHS/右除 LHS、sparse×sparse/mixed matrix product、双向 sparse/scalar 缩放及 compatible-size sparse `.*` scalar/dense/sparse 规则保持零 extent 与结果 storage | 可跨函数携带不可结构恢复 shape 的统一动态 NDArray/typed-array ABI、其余 sparse 操作与非整数幂、完整动态 bounds、值语义和 alias 契约 | | 函数 | 文件级 local function、前向调用、单/多输出 | `nargin`/`nargout`、`varargin`/`varargout`、function handle、anonymous/nested closure、workspace | -| JavaScript runtime | 内嵌数组、feature-gated complex object/numeric dispatch、checked non-enumerable shape descriptor、canonical CSC 直接 triplet construction/sparse transpose/indexing 与事务式 assignment/deletion、CSC×CSC scatter-accumulator、两类 nonzero-driven mixed matrix-product kernel,以及五种 sparse element-wise nonzero-driven kernel、广义 selector/section、zero-extent reshape/broadcast/transpose、结构感知实数方阵/矩形求解、CSC 三对角/行主元 LU 方阵求解、Hermitian/dense complex 方阵与 CPQR 复数矩形求解、real/complex 矩阵幂和基础 intrinsic runtime | 有版本的 Matlab runtime 包、统一 NDArray ABI、其余 sparse runtime、完整数值/异常兼容层、依赖与许可证审计 | +| JavaScript runtime | 内嵌数组、feature-gated complex object/numeric dispatch、checked non-enumerable shape descriptor、canonical CSC 直接 typed triplet construction/sparse transpose/indexing 与事务式 assignment/deletion、CSC×CSC scatter-accumulator、两类 nonzero-driven mixed matrix-product kernel,以及五种 sparse element-wise nonzero-driven kernel、广义 selector/section、zero-extent reshape/broadcast/transpose、结构感知实数方阵/矩形求解、CSC 三对角/行主元 LU 方阵求解、Hermitian/dense complex 方阵与 CPQR 复数矩形求解、real/complex 矩阵幂和基础 intrinsic runtime | 有版本的 Matlab runtime 包、统一 NDArray ABI、其余 sparse runtime、完整数值/异常兼容层、依赖与许可证审计 | | 验证 | Node.js、生成 C++、oracle、source map、专项 fuzz seed、跨层 storage 损坏事实拒绝、Matlab 编译性能发布阈值 | 授权 Matlab reference runner;真实项目 corpus;运行时性能、数值精度和内存发布阈值 | 矩阵 `*` 与逐元素 `.*` 保留不同源操作身份,并分别进入目标专属 runtime call plan。0.4.8 @@ -88,6 +88,13 @@ extent,并将双目标 LIR 提升到 v34。目标 `runtime_shape_arguments` 实参 ABI,`0×0 \ 0×n` 和 `m×0 / 0×0` 可分别保持 dense/CSC shaped-empty 结果;renderer 只序列化 该目标计划,不再读取 semantic storage/shape policy。complex 与动态 sparse shape 继续以 `MPF2054` 失败关闭。 +0.6.9 以 Semantic v22、MIR v28 和双目标 LIR v35 为 `SparseConstructionPlan` 增加 +`SparseValueDomain` 与 `SparseDuplicatePolicy`。静态 logical dense/triplet 输入直接产生 typed +canonical CSC,重复 logical triplet 采用 Matlab R2024 的 `any` 规则;转置、索引、mutation、 +reshape 与 `full` 全程保持 logical class。JavaScript CSC 携带不可省略的 `valueDomain`,`cpp` +使用 `sparse_matrix`;两个 representation planner 独立选择 runtime integer ABI/helper, +renderer 仍只序列化已验证目标计划。logical sparse 的算术、逐元素逻辑、归约、幂和求解没有 +随 storage 纵切面扩大,继续失败关闭。 一般 NDArray 表示与跨函数动态 shape 数据流、 command form、cell/struct/string 和异常结构仍不在当前可保持边界。因此文档、版本说明和 CLI 必须继续使用“已验证子集”的表述。 @@ -147,7 +154,7 @@ P0 完成前,产品定位保持“实验性已验证子集”。以下顺序 - [x] 建立 `[]` 的 `0×0` double 语义、静态零 extent array-literal/reshape/transpose/broadcast/section/growth plan 和 JavaScript shape descriptor - [ ] 建立可跨函数传播不可结构恢复 shape 的统一动态 NDArray ABI,并完成一般 assignment conformability - [x] 建立 logical/signed-integer/binary64 class 与 real/complex complexity 的正交可验证表示;complex 仅在 binary64 class 上有效 -- [x] 建立静态 real rank-2 canonical CSC 表示(包含零 extent)、dense/CSC conversion/query/count 与方阵 solve storage contract;`0×0` 系数的左右除按显式 shape ABI 保持 dense/CSC shaped-empty 结果 +- [x] 建立静态 real/logical rank-2 canonical CSC 表示(包含零 extent)、dense/CSC conversion/query/count 与方阵 solve storage contract;`0×0` 系数的左右除按显式 shape ABI 保持 dense/CSC shaped-empty 结果 - [x] 建立静态 real rank-2 CSC 只读 indexing:linear/subscript scalar、storage-preserving linear/submatrix selection、Matlab shape/orientation、direct CSC、O(nnz) full-colon、零 extent source/result、双目标独立 runtime 与逐层损坏计划拒绝 - [x] 建立静态 real rank-2 CSC reshape:size-vector/维度列表/单 `[]` 推断、列主序顺序保持、N 维请求折叠为二维 sparse 结果、零 extent 保持、O(nnz + result-columns) 双目标直接 CSC runtime 与逐层计划验证 - [x] 建立静态 finite-real rank-2 CSC 矩阵乘法:sparse×sparse→canonical CSC、两类 mixed product→dense,零 extent 结果保形、双目标独立 nonzero-driven runtime、逐层 storage policy、source map、差分、篡改拒错、fuzz 与性能预算 @@ -211,7 +218,7 @@ P0 完成前,产品定位保持“实验性已验证子集”。以下顺序 - [ ] 每个 P0 operator/index feature 至少覆盖标量、空值、shape 边界、错误类型、求值次数和副作用顺序 - [ ] 增加语法/AST 结构 fuzz、表达式/shape 语义 fuzz、runtime differential fuzz 和历史崩溃 corpus - [ ] source map 门禁覆盖续行、多语句行、local/nested function、生成 runtime wrapper 和异常栈 -- [x] 0.4.8 性能门禁增加 N 维 broadcast、转置、数组比较、逻辑索引和 `end` 的专属编译延迟、吞吐与产物大小预算;0.4.9 增加矩阵 solve/power、逐维 logical 与重复 numeric selector 编译场景;0.5.0 增加动态 `end` 读写和 runtime-shape broadcast 场景;0.5.1 增加 vector/matrix/N 维扩容与删除场景;0.5.2 增加重复零 extent reshape/broadcast/transpose/section/growth 场景;0.5.3 增加 rank/condition-aware solve;0.5.4 增加 diagonal/upper/lower/dense 结构分派、左右除和混合数值矩阵字面量场景;0.5.5 增加 pivoted-tridiagonal、Cholesky、对称不定回退及左右除场景;0.5.6 增加 logical kernel 与 `all`/`any` reduction kernel;0.5.7 增加 complex array/scalar/transpose 与跨函数动态 numeric kernel;0.5.8 增加 complex matrix multiply/Hermitian Cholesky/dense LU/left-right solve/integer-power kernel;0.5.9 增加 complex rectangular CPQR/multi-RHS/left-right solve kernel;0.6.0 增加 sparse CSC conversion/tridiagonal/general-LU square-solve kernel;0.6.1 在同一第 25 项 workload 中加入 zero/inferred/sized/reserved triplet construction、duplicate accumulation、full 与 sparse transpose;0.6.2 增加第 26 项 sparse indexing workload;0.6.3 增加第 27 项 sparse assignment workload;0.6.4 增加第 28 项 sparse reshape workload;0.6.5 增加第 29 项 sparse matrix-product workload;0.6.6 增加第 30 项 sparse scalar-product workload,并以 performance schema v3 为五个重型场景设置独立 latency/throughput/arena/generated-size 预算而不放宽其他 Matlab 场景门槛;0.6.7 增加第 31 项 sparse element-wise workload 及独立四维预算;0.6.8 在同一第 25 项 workload 中加入零维系数、dense/CSC RHS/LHS 与四种 shaped-empty 左右除,不新增或放宽预算 +- [x] 0.4.8 性能门禁增加 N 维 broadcast、转置、数组比较、逻辑索引和 `end` 的专属编译延迟、吞吐与产物大小预算;0.4.9 增加矩阵 solve/power、逐维 logical 与重复 numeric selector 编译场景;0.5.0 增加动态 `end` 读写和 runtime-shape broadcast 场景;0.5.1 增加 vector/matrix/N 维扩容与删除场景;0.5.2 增加重复零 extent reshape/broadcast/transpose/section/growth 场景;0.5.3 增加 rank/condition-aware solve;0.5.4 增加 diagonal/upper/lower/dense 结构分派、左右除和混合数值矩阵字面量场景;0.5.5 增加 pivoted-tridiagonal、Cholesky、对称不定回退及左右除场景;0.5.6 增加 logical kernel 与 `all`/`any` reduction kernel;0.5.7 增加 complex array/scalar/transpose 与跨函数动态 numeric kernel;0.5.8 增加 complex matrix multiply/Hermitian Cholesky/dense LU/left-right solve/integer-power kernel;0.5.9 增加 complex rectangular CPQR/multi-RHS/left-right solve kernel;0.6.0 增加 sparse CSC conversion/tridiagonal/general-LU square-solve kernel;0.6.1 在同一第 25 项 workload 中加入 zero/inferred/sized/reserved triplet construction、duplicate accumulation、full 与 sparse transpose;0.6.2 增加第 26 项 sparse indexing workload;0.6.3 增加第 27 项 sparse assignment workload;0.6.4 增加第 28 项 sparse reshape workload;0.6.5 增加第 29 项 sparse matrix-product workload;0.6.6 增加第 30 项 sparse scalar-product workload,并以 performance schema v3 为五个重型场景设置独立 latency/throughput/arena/generated-size 预算而不放宽其他 Matlab 场景门槛;0.6.7 增加第 31 项 sparse element-wise workload 及独立四维预算;0.6.8 在同一第 25 项 workload 中加入零维系数、dense/CSC RHS/LHS 与四种 shaped-empty 左右除,不新增或放宽预算;0.6.9 增加第 32 项 logical sparse storage workload 与独立四维预算 - [ ] 性能门禁继续覆盖大 dense array 执行、matrix multiply/section runtime、冷启动和运行时包体积 - [ ] 发布报告自动生成 Matlab feature manifest、reference 版本、差分 case 数、已知限制和性能变化 - [ ] P0 全部完成且连续发布门禁稳定后,才评估从“实验性子集”提升产品成熟度标记 @@ -250,7 +257,8 @@ P0 完成前,产品定位保持“实验性已验证子集”。以下顺序 - [x] 0.6.6 sparse scalar-product 纵切面:`sparse_csc_scale` 贯穿 Semantic v20、MIR v26 与双目标 LIR v32;双向 operand identity、CSC storage/shape、finite-real contract、纯 Emitter、目标独立 O(nnz) kernel、零因子 O(columns) 空 CSC 快路、source map、负向/跨层/LIR 损坏、差分、生成 nonfinite/overflow runtime 拒错、fuzz、架构与第 30 项性能预算完成 - [x] 0.6.7 sparse element-wise 纵切面:`SparseElementwisePlan` 贯穿 Semantic v21、MIR v27 与双目标 LIR v33;五种 scalar/dense/CSC operand form、compatible-size row/column expansion、canonical CSC result、纯 Emitter、目标独立 nonzero-driven kernel、source map、负向/跨层/LIR 损坏、差分、生成 nonfinite/overflow/计划污染拒错、fuzz、架构与第 31 项性能预算完成 - [x] 0.6.8 静态零 extent sparse 收尾:全部既有 CSC 操作保持显式 shape;双目标 LIR v34 固化 `runtime_shape_arguments`,`0×0` 方阵左右除及 dense/CSC shaped-empty 结果、纯 renderer、source map、跨层/LIR 损坏、双目标差分、生成计划拒错、fuzz、架构与 sparse-solve 性能 workload 完成 -- [ ] 随后纵切面:sparse logical/power/rectangular/complex、动态 sparse shape、病态特定解精确对齐、其余 numeric class、跨函数动态 NDArray shape ABI +- [x] 0.6.9 logical sparse storage 纵切面:`SparseValueDomain`/`SparseDuplicatePolicy` 贯穿 Semantic v22、MIR v28 与双目标 LIR v35;typed logical CSC 构造、duplicate `any`、transpose/index/mutation/reshape/`full` class 保持、纯 Emitter、source map、跨层/LIR 损坏、双目标差分、生成 shape-plan 拒错、fuzz、架构与第 32 项性能预算完成 +- [ ] 随后纵切面:sparse logical 算术/逐元素逻辑/归约、power/rectangular/complex、动态 sparse shape、病态特定解精确对齐、其余 numeric class、跨函数动态 NDArray shape ABI ## 官方语义索引 diff --git a/docs/TESTING.md b/docs/TESTING.md index e22cd97..5fc7704 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -11,17 +11,17 @@ MPF 的验证体系分为八层: 7. 小文件延迟、吞吐、深 CFG、大 shape、跨函数图、区域访问、CFG memory dependence、Matlab 数组/tensor/matrix-solve/dynamic-broadcast/complex kernel、峰值 arena、产物大小和并发 session 进入发布性能门禁。 8. Release 在标签提交上重新调用以上 canonical workflow;七类门禁全部成功后,三平台候选才可执行完整功能/差分测试、安装后外部消费、ZIP/许可证/版本/校验和验证、build-provenance attestation、发布及公开资产回读验证。 -当前 CFG memory-dependence 单元/负向测试覆盖 revision/count/density/sentinel、强类型 access site、incoming/outgoing adjacency、RAW/flow、WAR/anti、WAW/output、分支多定义合流、自然/不可归约 loop-carried、自环、unknown-memory barrier、同根 disjoint region 消边、full-root hazard 后 frontier kill、确定性 dump、`AnalysisManager` 缓存和损坏 edge 拒绝;`InstructionAttributes`、copy/writeback、跨函数 actual region、alias/conflict 与优化重映射继续回归。生产 API 测试要求编译报告公开 `mir-memory-dependence` stage 和分类计数。Python fuzz seed 覆盖分支/循环/索引写入,第八个 `memory-dependence` 性能场景同时要求最低依赖规模和非零 loop-carried 事实。0.4.8—0.5.6 已依次覆盖 implicit expansion、索引/shape mutation、empty array、rank/condition/structure-aware real solve、logical/reduction 与 portable scalar division。0.5.7 新增 `NumericClass`/`NumericComplexity` 在 Semantic v11、MIR v17、JavaScript LIR v23 与 `cpp` LIR v23 的传播和损坏事实拒绝;scanner/semantic 测试覆盖 imaginary literal、可遮蔽 `i`/`j`、complex 失败边界,集成/差分覆盖 scalar/array/transpose/indexed mutation/reshape/跨函数动态 numeric 与 source map,fuzz corpus 固定 complex seed,第二十二项性能场景覆盖 complex array/scalar/transpose 及动态函数 ABI。0.5.8 以 Semantic v12、MIR v18 和双目标 LIR v24 固化 matrix numeric domain/complex-square structure policy;单元与架构测试覆盖损坏 domain、runtime fragment 依赖和 feature pruning,差分覆盖 Hermitian Cholesky、dense pivoted LU、多 RHS、共轭转置右除、condition warning 与 safe-integer power,第 23 项性能场景覆盖 complex matrix kernel。0.5.9 以 Semantic v13、MIR v19 和双目标 LIR v25 固化矩形 `MatrixFactorizationPolicy`;损坏 factorization 逐层拒绝,差分覆盖 real/complex 超定、欠定、多 RHS、秩亏 warning 与共轭转置右除,新增 fuzz seed 和第 24 项 complex rectangular solve 性能场景。0.6.0 进一步以 Semantic v14、MIR v20 和双目标 LIR v26 固化 dense/CSC `ArrayStorageFormat` 及 sparse coefficient storage/factorization/structure policy;单元测试逐层注入损坏事实,集成测试固定后端隔离、source map 和失败关闭,差分/fuzz 覆盖 sparse square solve,第 25 项性能场景覆盖 CSC 转换、三对角快路和一般 sparse-row LU。0.6.1 以 Semantic v15、MIR v21 和双目标 LIR v27 固化 `SparseConstructionPlan`;单元与架构测试逐层篡改 arity/shape/cardinality/reserve/storage,集成与 source-map 测试验证目标隔离,差分/fuzz 固定五类 R2024 调用、scalar expansion、duplicate accumulation/cancellation 与 real sparse transpose,新增运行时越界拒绝,并扩展第 25 项性能 workload。0.6.2 以 Semantic v16、MIR v22 和双目标 LIR v28 固化 `SparseIndexPlan`;跨层损坏计划、alias/effect、source map、双目标差分、生成越界拒绝和 fuzz 固定 scalar/linear/submatrix selection 与 Matlab shape 规则,第 26 项性能场景使用 schema v3 独立预算。0.6.3 以 Semantic v17、MIR v23 和双目标 LIR v29 固化 `SparseMutationPlan`;逐层损坏计划、alias/effect、source map、双目标差分、非有限 replacement 生成拒错与 fuzz 固定 linear/subscript assignment、dense/sparse RHS、scalar expansion、duplicate last-write-wins、zero erase、growth、null deletion 与 self-alias,第 27 项性能场景继续使用 schema v3 独立预算。0.6.4 以 Semantic v18、MIR v24 和双目标 LIR v30 固化 `SparseReshapePlan`;size-vector、dimension-list、单 `[]` 推断、N 维请求折叠、alias/effect、跨层损坏、source map、双目标差分、生成计划篡改拒错与 fuzz 进入门禁,第 28 项性能场景使用 schema v3 独立预算。0.6.5 以 Semantic v19、MIR v25 和双目标 LIR v31 固化 `sparse_csc_multiply` storage policy;三种 storage 组合、跨层/LIR 损坏、source map、双目标差分、生成操作数篡改拒错与 fuzz 进入门禁,第 29 项性能场景使用独立 schema-v3 预算。0.6.6 以 Semantic v20、MIR v26 和双目标 LIR v32 固化 `sparse_csc_scale`;双向操作数、storage/shape、跨层/LIR 损坏、source map、差分、非有限与溢出生成拒错和 fuzz 进入门禁,第 30 项性能场景使用独立 schema-v3 预算。0.6.7 以 Semantic v21、MIR v27 和双目标 LIR v33 固化独立 `SparseElementwisePlan`;五种 scalar/dense/CSC operand form、compatible-size broadcast、canonical CSC result、跨层/LIR 损坏、source map、双目标差分、三类生成 runtime 拒错和 fuzz 进入门禁,第 31 项性能场景使用独立 schema-v3 预算。当前开发分支以双目标 LIR v34 固化可验证、可 dump 的 runtime shape 调用 ABI,并完成静态零 extent CSC 全路径及 `0×0` 方阵左右除;新增跨层/LIR 损坏、source map、双目标差分、两项生成 plan 拒错、fuzz 与既有第 25 项性能 workload 覆盖。MemorySSA/region-aware DCE/store forwarding 尚未启用,继续按 [TODO](../TODO.md) 推进。 +当前 CFG memory-dependence 单元/负向测试覆盖 revision/count/density/sentinel、强类型 access site、incoming/outgoing adjacency、RAW/flow、WAR/anti、WAW/output、分支多定义合流、自然/不可归约 loop-carried、自环、unknown-memory barrier、同根 disjoint region 消边、full-root hazard 后 frontier kill、确定性 dump、`AnalysisManager` 缓存和损坏 edge 拒绝;`InstructionAttributes`、copy/writeback、跨函数 actual region、alias/conflict 与优化重映射继续回归。生产 API 测试要求编译报告公开 `mir-memory-dependence` stage 和分类计数。Python fuzz seed 覆盖分支/循环/索引写入,第八个 `memory-dependence` 性能场景同时要求最低依赖规模和非零 loop-carried 事实。0.4.8—0.5.6 已依次覆盖 implicit expansion、索引/shape mutation、empty array、rank/condition/structure-aware real solve、logical/reduction 与 portable scalar division。0.5.7 新增 `NumericClass`/`NumericComplexity` 在 Semantic v11、MIR v17、JavaScript LIR v23 与 `cpp` LIR v23 的传播和损坏事实拒绝;scanner/semantic 测试覆盖 imaginary literal、可遮蔽 `i`/`j`、complex 失败边界,集成/差分覆盖 scalar/array/transpose/indexed mutation/reshape/跨函数动态 numeric 与 source map,fuzz corpus 固定 complex seed,第二十二项性能场景覆盖 complex array/scalar/transpose 及动态函数 ABI。0.5.8 以 Semantic v12、MIR v18 和双目标 LIR v24 固化 matrix numeric domain/complex-square structure policy;单元与架构测试覆盖损坏 domain、runtime fragment 依赖和 feature pruning,差分覆盖 Hermitian Cholesky、dense pivoted LU、多 RHS、共轭转置右除、condition warning 与 safe-integer power,第 23 项性能场景覆盖 complex matrix kernel。0.5.9 以 Semantic v13、MIR v19 和双目标 LIR v25 固化矩形 `MatrixFactorizationPolicy`;损坏 factorization 逐层拒绝,差分覆盖 real/complex 超定、欠定、多 RHS、秩亏 warning 与共轭转置右除,新增 fuzz seed 和第 24 项 complex rectangular solve 性能场景。0.6.0 进一步以 Semantic v14、MIR v20 和双目标 LIR v26 固化 dense/CSC `ArrayStorageFormat` 及 sparse coefficient storage/factorization/structure policy;单元测试逐层注入损坏事实,集成测试固定后端隔离、source map 和失败关闭,差分/fuzz 覆盖 sparse square solve,第 25 项性能场景覆盖 CSC 转换、三对角快路和一般 sparse-row LU。0.6.1 以 Semantic v15、MIR v21 和双目标 LIR v27 固化 `SparseConstructionPlan`;单元与架构测试逐层篡改 arity/shape/cardinality/reserve/storage,集成与 source-map 测试验证目标隔离,差分/fuzz 固定五类 R2024 调用、scalar expansion、duplicate accumulation/cancellation 与 real sparse transpose,新增运行时越界拒绝,并扩展第 25 项性能 workload。0.6.2 以 Semantic v16、MIR v22 和双目标 LIR v28 固化 `SparseIndexPlan`;跨层损坏计划、alias/effect、source map、双目标差分、生成越界拒绝和 fuzz 固定 scalar/linear/submatrix selection 与 Matlab shape 规则,第 26 项性能场景使用 schema v3 独立预算。0.6.3 以 Semantic v17、MIR v23 和双目标 LIR v29 固化 `SparseMutationPlan`;逐层损坏计划、alias/effect、source map、双目标差分、非有限 replacement 生成拒错与 fuzz 固定 linear/subscript assignment、dense/sparse RHS、scalar expansion、duplicate last-write-wins、zero erase、growth、null deletion 与 self-alias,第 27 项性能场景继续使用 schema v3 独立预算。0.6.4 以 Semantic v18、MIR v24 和双目标 LIR v30 固化 `SparseReshapePlan`;size-vector、dimension-list、单 `[]` 推断、N 维请求折叠、alias/effect、跨层损坏、source map、双目标差分、生成计划篡改拒错与 fuzz 进入门禁,第 28 项性能场景使用 schema v3 独立预算。0.6.5 以 Semantic v19、MIR v25 和双目标 LIR v31 固化 `sparse_csc_multiply` storage policy;三种 storage 组合、跨层/LIR 损坏、source map、双目标差分、生成操作数篡改拒错与 fuzz 进入门禁,第 29 项性能场景使用独立 schema-v3 预算。0.6.6 以 Semantic v20、MIR v26 和双目标 LIR v32 固化 `sparse_csc_scale`;双向操作数、storage/shape、跨层/LIR 损坏、source map、差分、非有限与溢出生成拒错和 fuzz 进入门禁,第 30 项性能场景使用独立 schema-v3 预算。0.6.7 以 Semantic v21、MIR v27 和双目标 LIR v33 固化独立 `SparseElementwisePlan`;五种 scalar/dense/CSC operand form、compatible-size broadcast、canonical CSC result、跨层/LIR 损坏、source map、双目标差分、三类生成 runtime 拒错和 fuzz 进入门禁,第 31 项性能场景使用独立 schema-v3 预算。0.6.8 以双目标 LIR v34 固化可验证、可 dump 的 runtime shape 调用 ABI,并完成静态零 extent CSC 全路径及 `0×0` 方阵左右除;跨层/LIR 损坏、source map、双目标差分、三项生成 plan 拒错、fuzz 与既有第 25 项性能 workload 同步覆盖。当前开发分支以 Semantic v22、MIR v28 和双目标 LIR v35 固化 `SparseValueDomain`/`SparseDuplicatePolicy`:logical CSC construction、duplicate `any`、类型保持的 transpose/index/mutation/reshape/`full` 进入逐层与目标 ABI 验证,第 99 项差分 case、第 19 项生成 runtime 拒绝、第 32 项性能场景及 source map/fuzz/架构门禁共同覆盖。MemorySSA/region-aware DCE/store forwarding 尚未启用,继续按 [TODO](../TODO.md) 推进。 ## 当前开发分支基线 | 指标 | 数量/结果 | |---|---:| -| C++ 单元与集成测试 | 268 项,零失败 | -| CTest | Debug/Release/RelWithDebInfo 均为 127 项;包含 98 项 differential、1 项 C++ 单元/集成、18 项生成 runtime 拒绝、1 项 fuzz smoke、1 项编译器分层门禁、1 项发布脚本正/负契约、2 项 CLI、1 项生成 C++ 编译、3 项后端隔离和 1 项安装后示例测试;非插桩性能发布门禁由独立目标执行,不重复计入普通 CTest | -| Differential corpus | Python 22、Fortran 19、Matlab 53、TypeScript 4,共 98 个 case | -| 工具完整环境执行路径 | 241 条程序路径,另有每 case 一条 oracle | -| 生产代码行覆盖率 | 当前开发分支实测 91.69%(37,154/40,520),硬门槛 85% | +| C++ 单元与集成测试 | 270 项,零失败 | +| CTest | Debug/Release/RelWithDebInfo 均为 129 项;包含 99 项 differential、1 项 C++ 单元/集成、19 项生成 runtime 拒绝、1 项 fuzz smoke、1 项编译器分层门禁、1 项发布脚本正/负契约、2 项 CLI、1 项生成 C++ 编译、3 项后端隔离和 1 项安装后示例测试;非插桩性能发布门禁由独立目标执行,不重复计入普通 CTest | +| Differential corpus | Python 22、Fortran 19、Matlab 54、TypeScript 4,共 99 个 case | +| 工具完整环境执行路径 | 243 条程序路径,另有每 case 一条 oracle | +| 生产代码行覆盖率 | 当前开发分支实测 91.74%(37,512/40,889),硬门槛 85% | ## Differential corpus @@ -29,10 +29,10 @@ MPF 的验证体系分为八层: - 22 个 Python case:CPython 3.14、Node.js、生成 C++17 与 oracle 四路比较; - 19 个 Fortran case:gfortran 严格 `-std=f2018` reference mode、Node.js、生成 C++17 与 oracle 四路比较;`MPF_FORTRAN_REFERENCE_STANDARD` 可在工具链支持后切换到 `f2023`; -- 53 个 Matlab case:Node.js、生成 C++17 与 oracle 三路比较; +- 54 个 Matlab case:Node.js、生成 C++17 与 oracle 三路比较; - 4 个 TypeScript case:Node.js 24 直接执行可擦除类型的 source、生成 JavaScript、生成 C++17 与声明式 oracle 四路比较;覆盖 basic、typed array、lexical block 和 canonical `for`,完整 type-check 仍待接入与 manifest 匹配的 `tsc`。 -在 Node.js、CPython 和 gfortran 均可用的工具完整环境中,这 98 个 case 共执行 241 条程序输出路径:98 条生成 JavaScript/Node.js、98 条生成 C++17、22 条 CPython、19 条 gfortran 和 4 条 Node.js source TypeScript 路径;此外每个 case 都有一条声明式 oracle 基线。runner 不仅分别检查 oracle,还直接比较可用执行路径。Matlab corpus 覆盖 `switch`、real matrix/solver/logical/reduction/index/shape/empty 既有语义;`sparse_construction_transpose.m` 固定五类 R2024 constructor 在当前静态实数 contract 内的 zero/empty、shape inference、scalar expansion、duplicate cancellation、`nzmax`、real transpose 与 canonical CSC;`sparse_indexing.m` 固定 scalar、colon、slice、numeric/logical、重复/乱序/空 selector、vector orientation、numeric-matrix shape 与 Cartesian submatrix,并验证 selection 继续保持 CSC;`sparse_assignment.m` 固定 linear/subscript assignment、dense/sparse RHS、scalar expansion、duplicate last-write-wins、zero erase、growth、column deletion 与 self-alias;`sparse_reshape.m` 固定 size vector、首尾 `[]` 推断、N 维请求折叠与 CSC 列主序保持;`sparse_matrix_product.m` 固定 CSC×CSC、CSC×dense、dense×CSC 的数值和 sparse/dense 结果 storage;`sparse_scalar_product.m` 固定 CSC×scalar、scalar×CSC、logical/negative/zero factor、exact-zero elimination 与 CSC storage;`sparse_elementwise_product.m` 固定 sparse/dense/scalar 五种 operand form、row/column compatible-size expansion、exact-zero removal 与 canonical CSC result;`sparse_square_solve.m` 固定 canonical CSC 转换/查询/计数、三对角与一般主元稀疏方阵左除、右除及 storage-preserving result;`sparse_zero_extent.m` 固定零 extent CSC 的 constructor、transpose、reshape、index、mutation、scale、element-wise、matrix-product 与 `0×0` 方阵左右除,并分别检查 dense/CSC shaped-empty 结果。两项 sparse condition case 继续固定精确奇异/近奇异警告正文和次数;`complex_numbers.m` 覆盖 imaginary literal、`i`/`j`、stable complex division、零指数、一元正负、`complex`/`conj`/`real`/`imag`/`abs`、complex compatible-size array、普通/共轭转置、索引写入/reshape,以及 scalar/array local-function 动态 numeric ABI;`complex_matrix_operations.m`、`complex_dense_pivot_solve.m` 和两项 condition-warning case 固定 complex matrix multiply、Hermitian Cholesky、dense pivoted LU、多 RHS、共轭转置右除、positive/negative power 与 warning;`complex_rectangular_solve.m` 和 `complex_rank_deficient_solve.m` 进一步覆盖复数超定/欠定、多 RHS、左右除、pivoted basic solution 与秩亏 warning。所有 case 均执行两个目标 runtime,另有 18 项生成 runtime 测试固定动态非法 shape/broadcast/division/power、sparse-product operand-plan、sparse-scale nonfinite scalar 与 overflow、sparse-elementwise nonfinite/overflow/plan-shape 污染,以及零 extent sparse 左右除 result-shape plan 污染边界。Python optimization case 固定 checked constant folding 在 source、生成 JavaScript 与生成 C++17 间的结果等价;comparisons case 四路覆盖 equality/identity/membership、list/tuple 种类差异、递归布尔/数值相等和混合 comparison chain;两项 Python 生成 runtime 测试分别要求 `/+0.0` 与 `//-0.0` 在 JavaScript/C++17 中非零退出并报告稳定的 division-by-zero 错误。TypeScript 四路覆盖 default/control/export、strict equality、typed array/零基 mutation、block-local 混合类型遮蔽、外层赋值以及 canonical-for 的 break/continue/update/退出值。Fortran disjoint-regions case 四路覆盖交错 stride 与二维同根 writable block,tensor、SELECT CASE、structured-unpacking、argument association 和 optional writeback cases 继续覆盖原契约。 +在 Node.js、CPython 和 gfortran 均可用的工具完整环境中,这 99 个 case 共执行 243 条程序输出路径:99 条生成 JavaScript/Node.js、99 条生成 C++17、22 条 CPython、19 条 gfortran 和 4 条 Node.js source TypeScript 路径;此外每个 case 都有一条声明式 oracle 基线。runner 不仅分别检查 oracle,还直接比较可用执行路径。Matlab corpus 覆盖 `switch`、real matrix/solver/logical/reduction/index/shape/empty 既有语义;`sparse_construction_transpose.m` 固定五类 R2024 constructor 在当前静态实数 contract 内的 zero/empty、shape inference、scalar expansion、duplicate cancellation、`nzmax`、real transpose 与 canonical CSC;`logical_sparse.m` 固定 logical dense/triplet construction、duplicate `any`、transpose、scalar/linear/submatrix selection、indexed mutation/growth/deletion、reshape、`full`/`issparse`/`nnz` 及 logical class 保持;`sparse_indexing.m` 固定 scalar、colon、slice、numeric/logical、重复/乱序/空 selector、vector orientation、numeric-matrix shape 与 Cartesian submatrix,并验证 selection 继续保持 CSC;`sparse_assignment.m` 固定 linear/subscript assignment、dense/sparse RHS、scalar expansion、duplicate last-write-wins、zero erase、growth、column deletion 与 self-alias;`sparse_reshape.m` 固定 size vector、首尾 `[]` 推断、N 维请求折叠与 CSC 列主序保持;`sparse_matrix_product.m` 固定 CSC×CSC、CSC×dense、dense×CSC 的数值和 sparse/dense 结果 storage;`sparse_scalar_product.m` 固定 CSC×scalar、scalar×CSC、logical/negative/zero factor、exact-zero elimination 与 CSC storage;`sparse_elementwise_product.m` 固定 sparse/dense/scalar 五种 operand form、row/column compatible-size expansion、exact-zero removal 与 canonical CSC result;`sparse_square_solve.m` 固定 canonical CSC 转换/查询/计数、三对角与一般主元稀疏方阵左除、右除及 storage-preserving result;`sparse_zero_extent.m` 固定零 extent CSC 的 constructor、transpose、reshape、index、mutation、scale、element-wise、matrix-product 与 `0×0` 方阵左右除,并分别检查 dense/CSC shaped-empty 结果。两项 sparse condition case 继续固定精确奇异/近奇异警告正文和次数;`complex_numbers.m` 覆盖 imaginary literal、`i`/`j`、stable complex division、零指数、一元正负、`complex`/`conj`/`real`/`imag`/`abs`、complex compatible-size array、普通/共轭转置、索引写入/reshape,以及 scalar/array local-function 动态 numeric ABI;`complex_matrix_operations.m`、`complex_dense_pivot_solve.m` 和两项 condition-warning case 固定 complex matrix multiply、Hermitian Cholesky、dense pivoted LU、多 RHS、共轭转置右除、positive/negative power 与 warning;`complex_rectangular_solve.m` 和 `complex_rank_deficient_solve.m` 进一步覆盖复数超定/欠定、多 RHS、左右除、pivoted basic solution 与秩亏 warning。所有 case 均执行两个目标 runtime,另有 19 项生成 runtime 测试固定动态非法 shape/broadcast/division/power、sparse-product operand-plan、sparse-scale nonfinite scalar 与 overflow、sparse-elementwise nonfinite/overflow/plan-shape 污染、零 extent sparse 左右除 result-shape plan 污染,以及 logical sparse shape-plan 污染边界。Python optimization case 固定 checked constant folding 在 source、生成 JavaScript 与生成 C++17 间的结果等价;comparisons case 四路覆盖 equality/identity/membership、list/tuple 种类差异、递归布尔/数值相等和混合 comparison chain;两项 Python 生成 runtime 测试分别要求 `/+0.0` 与 `//-0.0` 在 JavaScript/C++17 中非零退出并报告稳定的 division-by-zero 错误。TypeScript 四路覆盖 default/control/export、strict equality、typed array/零基 mutation、block-local 混合类型遮蔽、外层赋值以及 canonical-for 的 break/continue/update/退出值。Fortran disjoint-regions case 四路覆盖交错 stride 与二维同根 writable block,tensor、SELECT CASE、structured-unpacking、argument association 和 optional writeback cases 继续覆盖原契约。 每个 case 在 `build//differential//` 保存: @@ -70,7 +70,7 @@ build/fuzz/tests/mpf-transpiler-fuzzer build/fuzz/corpus ## 性能门禁 -`mpf.performance.release-gate` 运行两个目标的三十一类编译场景和八路并发 session,重复编译还会逐字节比较代码与 source map。场景覆盖 small、吞吐、深 CFG、大 shape、函数图、TypeScript 吞吐、128 个同根交错 section 调用的 storage-region 分析、branch/loop/index-write memory-dependence fixed point,以及 Matlab 数组、N 维 tensor、logical kernel、logical reduction kernel、矩阵 solve/power、rank-aware/秩亏 solve、condition-aware、diagonal/upper/lower/dense 与 pivoted-tridiagonal/Cholesky/对称不定回退结构感知方阵 solve、动态 `end`、runtime-shape broadcast、shape mutation、empty-array、complex scalar/array、complex square matrix、complex rectangular CPQR、sparse CSC square-solve、sparse matrix-product、sparse scalar-product、sparse element-wise product、sparse-index、sparse-assignment 及 sparse-reshape kernel;sparse-product workload 同时覆盖三种 storage 组合;sparse-elementwise workload 覆盖五种 operand form 和双轴广播;sparse-reshape workload 覆盖 size vector、推断维度、N 维请求折叠与反复 shape 恢复;sparse-solve workload 同时覆盖 zero/inferred/sized/reserved triplet construction、duplicate accumulation、full/sparse transpose、零维系数、dense/CSC RHS/LHS 与四种 shaped-empty 左右除。Matlab 二十三个场景另有独立的最大延迟、最低吞吐和最大产物预算,避免被全局宽阈值掩盖。结果写入 `build//performance-report.json`,并由 [`tests/performance/baseline.json`](../tests/performance/baseline.json) 的精确当前版本上限/下限检查延迟、吞吐、峰值 arena 和最大生成大小;performance schema v3 还允许为已命名的重型场景设置独立覆盖值;当前 sparse-index/sparse-assignment/sparse-reshape/sparse-multiply/sparse-scale/sparse-elementwise 覆盖不会放宽其余 Matlab 场景阈值,也不读取旧版本 baseline。性能 workflow 显式运行独立 `mpf-performance` 目标并归档机器可读报告;该非插桩门禁不在普通 CTest、coverage 或 ASan/UBSan 测试集中重复执行,避免重型测试争抢 CPU 后制造伪回归。 +`mpf.performance.release-gate` 运行两个目标的三十二类编译场景和八路并发 session,重复编译还会逐字节比较代码与 source map。场景覆盖 small、吞吐、深 CFG、大 shape、函数图、TypeScript 吞吐、128 个同根交错 section 调用的 storage-region 分析、branch/loop/index-write memory-dependence fixed point,以及 Matlab 数组、N 维 tensor、logical kernel、logical reduction kernel、矩阵 solve/power、rank-aware/秩亏 solve、condition-aware、diagonal/upper/lower/dense 与 pivoted-tridiagonal/Cholesky/对称不定回退结构感知方阵 solve、动态 `end`、runtime-shape broadcast、shape mutation、empty-array、complex scalar/array、complex square matrix、complex rectangular CPQR、sparse CSC square-solve、sparse matrix-product、sparse scalar-product、sparse element-wise product、sparse-index、sparse-assignment、sparse-reshape 及 logical-sparse kernel;sparse-product workload 同时覆盖三种 storage 组合;sparse-elementwise workload 覆盖五种 operand form 和双轴广播;sparse-reshape workload 覆盖 size vector、推断维度、N 维请求折叠与反复 shape 恢复;sparse-solve workload 同时覆盖 zero/inferred/sized/reserved triplet construction、duplicate accumulation、full/sparse transpose、零维系数、dense/CSC RHS/LHS 与四种 shaped-empty 左右除;logical-sparse workload 覆盖 logical dense/triplet construction、duplicate `any` 及完整 storage lifecycle。Matlab 二十四个场景另有独立的最大延迟、最低吞吐和最大产物预算,避免被全局宽阈值掩盖。结果写入 `build//performance-report.json`,并由 [`tests/performance/baseline.json`](../tests/performance/baseline.json) 的精确当前版本上限/下限检查延迟、吞吐、峰值 arena 和最大生成大小;performance schema v3 还允许为已命名的重型场景设置独立覆盖值;当前 sparse-index/sparse-assignment/sparse-reshape/sparse-multiply/sparse-scale/sparse-elementwise/logical-sparse 覆盖不会放宽其余 Matlab 场景阈值,也不读取旧版本 baseline。性能 workflow 显式运行独立 `mpf-performance` 目标并归档机器可读报告;该非插桩门禁不在普通 CTest、coverage 或 ASan/UBSan 测试集中重复执行,避免重型测试争抢 CPU 后制造伪回归。 质量与覆盖率门禁: diff --git a/docs/VERSIONING.md b/docs/VERSIONING.md index 6385877..5e57b88 100644 --- a/docs/VERSIONING.md +++ b/docs/VERSIONING.md @@ -5,7 +5,7 @@ MPF 的开发版本从 `0.0.1` 开始,使用三段十进制版本号 `MAJOR.MI - `0.0.1` 是首个开发版本; - `0.0.9` 的下一个版本是 `0.1.0`; - `0.2.9` 的下一个版本是 `0.3.0`; -- 当前开发版本是 `0.6.8`;下一个版本是 `0.6.9`。 +- 当前开发版本是 `0.6.9`;下一个版本是 `0.7.0`。 CMake `project(VERSION)` 是源码树的唯一版本源。配置阶段由它生成公开 `mpf/version.hpp`、CMake package version 和 CLI 版本;测试不得复制硬编码版本字符串。配置会拒绝 `0.0.0` 以及 patch 大于 9 的版本。 @@ -20,6 +20,6 @@ MPF 尚未形成可发布产品。每个 0.x 版本都是一个精确的开发 Matlab、Python、Fortran 和 TypeScript 的旧语言标准支持是产品输入语义,不属于旧 MPF 兼容层;相关版本 gate 继续按语言 manifest 和官方 grammar 建设。 -Git tag 直接使用 `MAJOR.MINOR.PATCH`,不添加 `v` 前缀,并且正式发布只接受位于 `main` 历史上的 annotated tag。`CHANGELOG.md` 和 `CHANGELOG-ZH.md` 不设置待发布占位段,必须直接以当前 CMake 项目版本标题开头且条目数一致。GitHub Release 正文由 Bash 脚本提取 `CHANGELOG.md` 中当前标签版本标题下、下一个版本标题前的内容,不自动生成通用 release notes。changelog 保存开发快照的工程变化,但任何历史条目都不构成跨版本兼容承诺。发布检查必须保证 tag、CMake、CLI、安装包、性能基线和 changelog 版本完全一致;七类 canonical workflow 会在标签 SHA 上重新执行,随后才允许三平台候选测试、安装后 consumer 验证、制品校验、来源证明、发布和公开资产回验。 +Git tag 直接使用 `MAJOR.MINOR.PATCH`,不添加 `v` 前缀,并且正式发布只接受位于 `main` 历史上的 annotated tag。`CHANGELOG.md` 和 `CHANGELOG-ZH.md` 不设置待发布占位段,必须直接以当前 CMake 项目版本标题开头且条目数一致。GitHub Release 正文由 Bash 脚本提取 `CHANGELOG.md` 中当前标签版本标题下、下一个版本标题前的内容,不自动生成通用 release notes。changelog 只保存用户可感知的能力、行为、兼容性、性能或缺陷修复;测试数量、覆盖率数值、门禁清单等内部验证元数据进入测试/发布报告,不进入 changelog 或 Release 正文。任何历史条目都不构成跨版本兼容承诺。发布检查必须保证 tag、CMake、CLI、安装包、性能基线和 changelog 版本完全一致;七类 canonical workflow 会在标签 SHA 上重新执行,随后才允许三平台候选测试、安装后 consumer 验证、制品校验、来源证明、发布和公开资产回验。 每个版本的 changelog 应整理为 **8—20 条**用户可理解、可独立验证的更新。达到 8 条即可形成新版本;超过 20 条时应拆分版本或合并过细条目。条目数量不替代测试、覆盖率、性能和制品门禁。Release workflow 会拒绝条目数量不合规、标题不在文件首行或版本身份不一致的源码树。 diff --git a/examples/matlab/logical_sparse.m b/examples/matlab/logical_sparse.m new file mode 100644 index 0000000..050dc8f --- /dev/null +++ b/examples/matlab/logical_sparse.m @@ -0,0 +1,11 @@ +dense = sparse([true false; false true]); +duplicates = sparse([1 1 2 2], [1 1 2 2], [false true false true], 2, 2); +transposed = dense.'; +selected = duplicates([2 1], [2 1]); +reshaped = reshape(selected, [1 4]); +duplicates(1, 2) = true; +duplicates(2, 2) = false; +dense_full = full(dense); +result_full = full(reshaped); +disp(nnz(dense), nnz(duplicates), nnz(transposed), nnz(selected), nnz(reshaped), ... + nnz(dense_full), nnz(result_full)) diff --git a/src/backends/common/lir_builder.hpp b/src/backends/common/lir_builder.hpp index 13e7896..4b08403 100644 --- a/src/backends/common/lir_builder.hpp +++ b/src/backends/common/lir_builder.hpp @@ -154,6 +154,8 @@ LirExpression lower_lir_expression(const mir::Program& program, const MirExpress result.sparse_construction.triplet_element_counts = attributes.sparse_construction.triplet_element_counts; result.sparse_construction.reserve_hint = attributes.sparse_construction.reserve_hint; + result.sparse_construction.value_domain = attributes.sparse_construction.value_domain; + result.sparse_construction.duplicate_policy = attributes.sparse_construction.duplicate_policy; } if (attributes.sparse_index.valid()) { result.sparse_index.kind = attributes.sparse_index.kind; diff --git a/src/backends/common/lir_dump.hpp b/src/backends/common/lir_dump.hpp index c90cd95..fb699a7 100644 --- a/src/backends/common/lir_dump.hpp +++ b/src/backends/common/lir_dump.hpp @@ -184,7 +184,9 @@ void dump_target_expression(std::ostream& output, const Expression& expression, if (index != 0U) output << ','; output << expression.sparse_construction.triplet_element_counts[index]; } - output << "] reserve " << expression.sparse_construction.reserve_hint; + output << "] reserve " << expression.sparse_construction.reserve_hint << " value-domain " + << static_cast(expression.sparse_construction.value_domain) << " duplicate-policy " + << static_cast(expression.sparse_construction.duplicate_policy); } if (expression.sparse_index.valid()) { output << " sparse-index " << static_cast(expression.sparse_index.kind) << " input ["; @@ -365,7 +367,7 @@ void dump_target_statements(std::ostream& output, const std::vector& template void dump_target_lir_body(std::ostream& output, const Program& program, const std::string_view target) { - output << target << "-semantic-lir-v34 revision " << program.revision << " nodes " + output << target << "-semantic-lir-v35 revision " << program.revision << " nodes " << program.node_count << " runtime 0x" << std::hex << program.runtime.bits << std::dec << '\n'; output << "dependencies"; diff --git a/src/backends/cpp/lir.hpp b/src/backends/cpp/lir.hpp index 6fdc3b6..7c055c1 100644 --- a/src/backends/cpp/lir.hpp +++ b/src/backends/cpp/lir.hpp @@ -303,6 +303,8 @@ struct SparseConstructionPlan { std::vector result_shape; std::vector triplet_element_counts; std::size_t reserve_hint{0U}; + semantic::SparseValueDomain value_domain{semantic::SparseValueDomain::none}; + semantic::SparseDuplicatePolicy duplicate_policy{semantic::SparseDuplicatePolicy::none}; [[nodiscard]] bool valid() const noexcept { return kind != semantic::SparseConstructionKind::none; diff --git a/src/backends/cpp/lir_representation.cpp b/src/backends/cpp/lir_representation.cpp index 7a5c66e..aa463c7 100644 --- a/src/backends/cpp/lir_representation.cpp +++ b/src/backends/cpp/lir_representation.cpp @@ -481,7 +481,8 @@ bool valid_sparse_elementwise_plan(const lir::Expression& expression) noexcept { std::optional sparse_argument_count(const lir::Expression& expression) noexcept { if (expression.inferred_type != ValueType::list) { - return expression.inferred_type == ValueType::integer || + return expression.inferred_type == ValueType::boolean || + expression.inferred_type == ValueType::integer || expression.inferred_type == ValueType::real ? std::optional{1U} : std::nullopt; @@ -497,6 +498,22 @@ std::optional sparse_argument_count(const lir::Expression& expressi return count; } +semantic::SparseValueDomain sparse_value_domain(const lir::Expression& expression) noexcept { + const auto type = expression.inferred_type == ValueType::list ? expression.element_type + : expression.inferred_type; + const auto numeric_type = expression.inferred_type == ValueType::list + ? expression.element_numeric_type + : expression.numeric_type; + if (type == ValueType::boolean && numeric_type == logical_numeric_type) { + return semantic::SparseValueDomain::logical; + } + if ((type == ValueType::integer || type == ValueType::real) && + numeric_type.complexity == NumericComplexity::real) { + return semantic::SparseValueDomain::finite_real; + } + return semantic::SparseValueDomain::none; +} + semantic::SparseConstructionKind expected_sparse_construction_kind( const lir::Expression& expression) noexcept { if (expression.kind != ExpressionKind::call || expression.children.empty()) { @@ -521,11 +538,15 @@ bool valid_sparse_construction(const lir::Expression& expression) noexcept { if (sparse.kind != expected_sparse_construction_kind(expression)) return false; if (!sparse.valid()) { return sparse.result_shape.empty() && sparse.triplet_element_counts.empty() && - sparse.reserve_hint == 0U; + sparse.reserve_hint == 0U && sparse.value_domain == semantic::SparseValueDomain::none && + sparse.duplicate_policy == semantic::SparseDuplicatePolicy::none; } bool valid = expression.inferred_type == ValueType::list && expression.array_storage == ArrayStorageFormat::sparse_csc && sparse.result_shape == expression.shape && sparse.result_shape.size() == 2U && + sparse.value_domain == sparse_value_domain(expression) && + semantic::valid_sparse_construction_value_contract(sparse.kind, sparse.value_domain, + sparse.duplicate_policy) && std::none_of(sparse.result_shape.begin(), sparse.result_shape.end(), [](const auto extent) { return extent == dynamic_extent; }); const bool triplets = sparse.kind == semantic::SparseConstructionKind::triplets_inferred || @@ -549,7 +570,11 @@ bool valid_sparse_construction(const lir::Expression& expression) noexcept { } if (sparse.kind == semantic::SparseConstructionKind::dense_conversion) { valid = valid && expression.children.size() == 2U && - expression.children[1].shape == sparse.result_shape; + expression.children[1].shape == sparse.result_shape && + sparse.value_domain == sparse_value_domain(expression.children[1]); + } else if (triplets) { + valid = valid && expression.children.size() >= 4U && + sparse.value_domain == sparse_value_domain(expression.children[3]); } return valid && (sparse.kind == semantic::SparseConstructionKind::triplets_reserved || sparse.reserve_hint == 0U); @@ -583,13 +608,14 @@ bool valid_sparse_reshape(const lir::Expression& expression) noexcept { const auto expected_inference = empty_dimensions == 1U ? semantic::SparseReshapeInference::one_dimension : semantic::SparseReshapeInference::none; - return source.inferred_type == ValueType::list && source.element_type == ValueType::real && - source.element_numeric_type == real_numeric_type && + return source.inferred_type == ValueType::list && + semantic::valid_sparse_stored_value_type(source.element_type, + source.element_numeric_type) && expression.inferred_type == ValueType::list && - expression.element_type == ValueType::real && - expression.element_numeric_type == real_numeric_type && expression.column_major && - sparse.dimension_form == expected_form && empty_dimensions <= 1U && - sparse.inference == expected_inference && + expression.element_type == source.element_type && + expression.element_numeric_type == source.element_numeric_type && + expression.column_major && sparse.dimension_form == expected_form && + empty_dimensions <= 1U && sparse.inference == expected_inference && (expected_inference == semantic::SparseReshapeInference::none || sparse.inferred_axis == empty_axis) && sparse.input_shape == source.shape && sparse.source_storage == source.array_storage && @@ -1000,6 +1026,19 @@ lir::ExpressionPlan expected_expression_plan( semantic::SparseConstructionKind::dense_conversion) { result.runtime_shape_arguments = {expression.sparse_construction.result_shape}; } + if (result.call == lir::CallForm::matlab_sparse) { + if (!result.runtime_shape_arguments.empty()) { + result.token = + expression.sparse_construction.value_domain == semantic::SparseValueDomain::logical + ? "mpf_runtime::sparse_logical_from_dense" + : "mpf_runtime::sparse_from_dense"; + } else { + result.token = expression.sparse_construction.duplicate_policy == + semantic::SparseDuplicatePolicy::logical_any + ? "mpf_runtime::sparse_logical_any" + : "mpf_runtime::sparse"; + } + } result.call_arguments.reserve(expression.argument_transfers.size()); for (const auto transfer : expression.argument_transfers) { lir::CallArgumentPlan argument; @@ -1336,8 +1375,8 @@ void verify_expression(const lir::Expression& expression, const lir::EmissionPla } else if (expression.sparse_index.valid()) { const auto& source = expression.children.front(); const auto scalar = semantic::sparse_index_returns_scalar(expression.sparse_index.kind); - if (source.element_type != ValueType::real || - source.element_numeric_type != real_numeric_type || + if (!semantic::valid_sparse_stored_value_type(source.element_type, + source.element_numeric_type) || expression.sparse_index.input_shape != source.shape || expression.sparse_index.source_storage != source.array_storage || expression.sparse_index.result_shape != expression.shape || diff --git a/src/backends/cpp/renderer.cpp b/src/backends/cpp/renderer.cpp index 996f3e6..243b443 100644 --- a/src/backends/cpp/renderer.cpp +++ b/src/backends/cpp/renderer.cpp @@ -526,7 +526,7 @@ class Renderer final { return; case cpp::lir::CallForm::matlab_sparse: if (!expression.plan.runtime_shape_arguments.empty()) { - output_ << "mpf_runtime::sparse_from_dense("; + output_ << expression.plan.token << '('; emit_expression(expression.children[1]); for (const auto& shape : expression.plan.runtime_shape_arguments) { output_ << ", "; @@ -535,7 +535,7 @@ class Renderer final { output_ << ')'; return; } - output_ << "mpf_runtime::sparse("; + output_ << expression.plan.token << '('; for (std::size_t index = 1U; index < expression.children.size(); ++index) { if (index != 1U) output_ << ", "; emit_expression(expression.children[index]); diff --git a/src/backends/cpp/sparse_matrix_runtime.cpp b/src/backends/cpp/sparse_matrix_runtime.cpp index 5a3c73c..7db2bc5 100644 --- a/src/backends/cpp/sparse_matrix_runtime.cpp +++ b/src/backends/cpp/sparse_matrix_runtime.cpp @@ -31,7 +31,7 @@ template void validate_sparse_csc( if (row >= matrix.rows || (has_previous && row <= previous) || !std::isfinite(value) || value == 0.0) throw std::invalid_argument("MPF Matlab " + name + - " is not canonical finite-real CSC"); + " is not canonical finite-real/logical CSC"); previous = row; has_previous = true; } } @@ -56,12 +56,15 @@ struct sparse_triplet_entry { double value{}; }; template -const auto& sparse_triplet_value_at(const Values& values, const std::size_t index) { - if constexpr (is_vector>::value) return values.at(index); - else return values.front(); -} -template -sparse_matrix sparse_from_triplets( +double sparse_triplet_value_at(const Values& values, const std::size_t index) { + if constexpr (is_vector>::value) + return static_cast(values.at(index)); + else + return static_cast(values.front()); +} +template +sparse_matrix sparse_from_triplets_impl( const RowArgument& row_argument, const ColumnArgument& column_argument, const ValueArgument& value_argument, const std::optional explicit_rows = {}, const std::optional explicit_columns = {}, @@ -85,12 +88,9 @@ sparse_matrix sparse_from_triplets( entries.reserve(count); std::size_t inferred_rows = 0U; std::size_t inferred_columns = 0U; for (std::size_t index = 0; index < count; ++index) { - const auto row_numeric = static_cast( - sparse_triplet_value_at(row_values, index)); - const auto column_numeric = static_cast( - sparse_triplet_value_at(column_values, index)); - const auto stored_numeric = static_cast( - sparse_triplet_value_at(stored_values, index)); + const auto row_numeric = sparse_triplet_value_at(row_values, index); + const auto column_numeric = sparse_triplet_value_at(column_values, index); + const auto stored_numeric = sparse_triplet_value_at(stored_values, index); if (!std::isfinite(row_numeric) || std::trunc(row_numeric) != row_numeric || row_numeric <= 0.0 || row_numeric > 9007199254740991.0 || !std::isfinite(column_numeric) || std::trunc(column_numeric) != column_numeric || @@ -119,7 +119,7 @@ sparse_matrix sparse_from_triplets( return left.column < right.column || (left.column == right.column && left.row < right.row); }); - sparse_matrix result; + sparse_matrix result; result.rows = rows; result.columns = columns; const auto capacity = std::max(count, reserve_hint.value_or(count)); result.row_indices.reserve(capacity); result.values.reserve(capacity); @@ -127,43 +127,83 @@ sparse_matrix sparse_from_triplets( std::size_t entry = 0U; for (std::size_t column = 0; column < columns; ++column) { while (entry < entries.size() && entries[entry].column == column) { - const auto row = entries[entry].row; double sum = 0.0; + const auto row = entries[entry].row; double sum = 0.0; bool any = false; while (entry < entries.size() && entries[entry].column == column && - entries[entry].row == row) - sum += entries[entry++].value; - if (!std::isfinite(sum)) - throw std::invalid_argument( - "MPF Matlab sparse duplicate accumulation is not finite"); - if (sum != 0.0) { result.row_indices.push_back(row); result.values.push_back(sum); } + entries[entry].row == row) { + const auto stored = entries[entry++].value; + if constexpr (LogicalAny) any = any || stored != 0.0; + else sum += stored; + } + if constexpr (!LogicalAny) { + if (!std::isfinite(sum)) + throw std::invalid_argument( + "MPF Matlab sparse duplicate accumulation is not finite"); + } + const auto keep = LogicalAny ? any : sum != 0.0; + if (keep) { + result.row_indices.push_back(row); + result.values.push_back(static_cast(LogicalAny ? any : sum)); + } } result.column_pointers.push_back(result.values.size()); } validate_sparse_csc(result); return result; } -template sparse_matrix sparse( +template +sparse_matrix sparse_from_triplets( + const RowArgument& row_argument, const ColumnArgument& column_argument, + const ValueArgument& value_argument, const std::optional explicit_rows = {}, + const std::optional explicit_columns = {}, + const std::optional reserve_hint = {}) { + return sparse_from_triplets_impl(row_argument, column_argument, value_argument, + explicit_rows, explicit_columns, reserve_hint); +} +template +sparse_matrix sparse_logical_from_triplets( + const RowArgument& row_argument, const ColumnArgument& column_argument, + const ValueArgument& value_argument, const std::optional explicit_rows = {}, + const std::optional explicit_columns = {}, + const std::optional reserve_hint = {}) { + return sparse_from_triplets_impl(row_argument, column_argument, value_argument, + explicit_rows, explicit_columns, reserve_hint); +} +template sparse_matrix sparse_from_matrix_impl( const std::vector>& values) { const auto dense = matlab_dense_matrix(values, "sparse input"); - sparse_matrix result; + sparse_matrix result; result.rows = dense.size(); result.columns = dense.front().size(); result.column_pointers.push_back(0U); for (std::size_t column = 0; column < result.columns; ++column) { for (std::size_t row = 0; row < result.rows; ++row) { if (dense[row][column] == 0.0) continue; - result.row_indices.push_back(row); result.values.push_back(dense[row][column]); + result.row_indices.push_back(row); + result.values.push_back(static_cast(dense[row][column])); } result.column_pointers.push_back(result.values.size()); } + validate_sparse_csc(result); return result; } +template sparse_matrix sparse( + const std::vector>& values) { + return sparse_from_matrix_impl(values); +} template sparse_matrix sparse(const std::vector& values) { return sparse(std::vector>{values}); } +template sparse_matrix sparse_logical( + const std::vector>& values) { + return sparse_from_matrix_impl(values); +} +template sparse_matrix sparse_logical(const std::vector& values) { + return sparse_logical(std::vector>{values}); +} template sparse_matrix sparse(const sparse_matrix& matrix) { validate_sparse_csc(matrix); return matrix; } -template -sparse_matrix sparse_from_dense( +template +sparse_matrix sparse_from_dense_impl( const Values& values, const std::array& planned_shape) { if (planned_shape[0] > 9007199254740991ULL || planned_shape[1] > 9007199254740991ULL || (planned_shape[0] != 0U && @@ -178,7 +218,7 @@ sparse_matrix sparse_from_dense( actual_shape != std::vector(planned_shape.begin(), planned_shape.end()))) throw std::invalid_argument( "MPF Matlab sparse input disagrees with its static shape contract"); - sparse_matrix result; + sparse_matrix result; result.rows = planned_shape[0]; result.columns = planned_shape[1]; result.column_pointers.reserve(result.columns + 1U); result.column_pointers.push_back(0U); @@ -187,13 +227,26 @@ sparse_matrix sparse_from_dense( const auto numeric = static_cast(flattened[row + column * result.rows]); if (!std::isfinite(numeric)) throw std::invalid_argument("MPF Matlab sparse input requires finite real values"); - if (numeric != 0.0) { result.row_indices.push_back(row); result.values.push_back(numeric); } + if (numeric != 0.0) { + result.row_indices.push_back(row); + result.values.push_back(static_cast(numeric)); + } } result.column_pointers.push_back(result.values.size()); } validate_sparse_csc(result); return result; } +template +sparse_matrix sparse_from_dense( + const Values& values, const std::array& planned_shape) { + return sparse_from_dense_impl(values, planned_shape); +} +template +sparse_matrix sparse_logical_from_dense( + const Values& values, const std::array& planned_shape) { + return sparse_from_dense_impl(values, planned_shape); +} template sparse_matrix sparse_from_dense( const sparse_matrix& matrix, const std::array& planned_shape) { @@ -203,6 +256,10 @@ sparse_matrix sparse_from_dense( "MPF Matlab sparse input disagrees with its static shape contract"); return matrix; } +inline sparse_matrix sparse_logical_from_dense( + const sparse_matrix& matrix, const std::array& planned_shape) { + return sparse_from_dense(matrix, planned_shape); +} template sparse_matrix sparse(const Rows& rows_value, const Columns& columns_value) { const auto rows = sparse_dimension(rows_value, "row extent", true); @@ -216,6 +273,11 @@ template sparse_matrix sparse(const Row& rows, const Column& columns, const Value& values) { return sparse_from_triplets(rows, columns, values); } +template +sparse_matrix sparse_logical_any(const Row& rows, const Column& columns, + const Value& values) { + return sparse_logical_from_triplets(rows, columns, values); +} template sparse_matrix sparse(const Row& row_values, const Column& column_values, const Value& values, const Rows& rows_value, @@ -224,6 +286,15 @@ sparse_matrix sparse(const Row& row_values, const Column& column_values, sparse_dimension(rows_value, "row extent", true), sparse_dimension(columns_value, "column extent", true)); } +template +sparse_matrix sparse_logical_any( + const Row& row_values, const Column& column_values, const Value& values, + const Rows& rows_value, const Columns& columns_value) { + return sparse_logical_from_triplets( + row_values, column_values, values, + sparse_dimension(rows_value, "row extent", true), + sparse_dimension(columns_value, "column extent", true)); +} template sparse_matrix sparse(const Row& row_values, const Column& column_values, @@ -234,14 +305,25 @@ sparse_matrix sparse(const Row& row_values, const Column& column_values, sparse_dimension(columns_value, "column extent", true), sparse_dimension(reserve_value, "nzmax", true)); } -template std::vector> full(const sparse_matrix& value) { +template +sparse_matrix sparse_logical_any( + const Row& row_values, const Column& column_values, const Value& values, + const Rows& rows_value, const Columns& columns_value, const Reserve& reserve_value) { + return sparse_logical_from_triplets( + row_values, column_values, values, + sparse_dimension(rows_value, "row extent", true), + sparse_dimension(columns_value, "column extent", true), + sparse_dimension(reserve_value, "nzmax", true)); +} +template std::vector> full(const sparse_matrix& value) { validate_sparse_csc(value, "full input"); const auto& matrix = value; - std::vector> result(matrix.rows, std::vector(matrix.columns)); + std::vector> result(matrix.rows, std::vector(matrix.columns)); for (std::size_t column = 0; column < matrix.columns; ++column) for (auto index = matrix.column_pointers[column]; index < matrix.column_pointers[column + 1U]; ++index) - result[matrix.row_indices[index]][column] = static_cast(matrix.values[index]); + result[matrix.row_indices[index]][column] = matrix.values[index]; return result; } template @@ -704,7 +786,11 @@ template std::size_t nnz(const sparse_matrix& matrix) { template std::size_t nnz(const T& value) { if constexpr (is_vector>::value) { std::size_t count = 0U; - for (const auto& item : value) count += nnz(item); + if constexpr (std::is_same_v, std::vector>) { + for (const bool item : value) count += item ? 1U : 0U; + } else { + for (const auto& item : value) count += nnz(item); + } return count; } else { static_assert(std::is_arithmetic_v>, @@ -715,10 +801,10 @@ template std::size_t nnz(const T& value) { return numeric != 0.0 ? 1U : 0U; } } -template sparse_matrix sparse_transpose(const sparse_matrix& value) { +template sparse_matrix sparse_transpose(const sparse_matrix& value) { validate_sparse_csc(value, "transpose operand"); const auto& matrix = value; - sparse_matrix result; + sparse_matrix result; result.rows = matrix.columns; result.columns = matrix.rows; result.column_pointers.assign(result.columns + 1U, 0U); for (const auto row : matrix.row_indices) ++result.column_pointers[row + 1U]; @@ -731,7 +817,7 @@ template sparse_matrix sparse_transpose(const sparse_matrix index < matrix.column_pointers[column + 1U]; ++index) { const auto target = next[matrix.row_indices[index]]++; result.row_indices[target] = column; - result.values[target] = static_cast(matrix.values[index]); + result.values[target] = matrix.values[index]; } return result; } @@ -750,7 +836,7 @@ std::size_t sparse_reshape_shape_count(const std::array& shap return count; } template -sparse_matrix sparse_reshape( +sparse_matrix sparse_reshape( const sparse_matrix& value, const std::array& input_shape, const std::array& requested_shape, const std::array& result_shape) { @@ -775,7 +861,7 @@ sparse_matrix sparse_reshape( if (result_shape[0] != rows || result_shape[1] != columns) throw std::invalid_argument( "MPF Matlab sparse reshape result plan does not match requested dimensions"); - sparse_matrix result; + sparse_matrix result; result.rows = rows; result.columns = columns; result.column_pointers.assign(columns + 1U, 0U); @@ -788,7 +874,7 @@ sparse_matrix sparse_reshape( const auto linear = value.row_indices[index] + column * value.rows; const auto reshaped_column = linear / rows; result.row_indices[output] = linear % rows; - result.values[output] = static_cast(value.values[index]); + result.values[output] = value.values[index]; ++result.column_pointers[reshaped_column + 1U]; ++output; } @@ -798,17 +884,17 @@ sparse_matrix sparse_reshape( validate_sparse_csc(result, "reshape result"); return result; } -template double sparse_value_at(const sparse_matrix& matrix, - const std::size_t row, - const std::size_t column) { +template T sparse_value_at(const sparse_matrix& matrix, + const std::size_t row, + const std::size_t column) { const auto first = matrix.row_indices.begin() + static_cast(matrix.column_pointers[column]); const auto last = matrix.row_indices.begin() + static_cast(matrix.column_pointers[column + 1U]); const auto found = std::lower_bound(first, last, row); - if (found == last || *found != row) return 0.0; + if (found == last || *found != row) return T{}; const auto offset = static_cast(found - matrix.row_indices.begin()); - return static_cast(matrix.values[offset]); + return matrix.values[offset]; } inline std::size_t sparse_element_count(const std::size_t rows, const std::size_t columns) { if (rows != 0U && columns > std::numeric_limits::max() / rows) @@ -848,7 +934,7 @@ inline bool sparse_is_full_slice(const slice_selector& selector) { return !selector.start.has_value() && !selector.stop.has_value() && !selector.step.has_value() && selector.inclusive; } -template double sparse_linear_element( +template T sparse_linear_element( const sparse_matrix& value, const Selector& selector, const std::size_t base = 1U) { validate_sparse_csc(value, "linear index operand"); const auto size = sparse_element_count(value.rows, value.columns); @@ -864,10 +950,10 @@ template double sparse_linear_element( return sparse_value_at(value, linear % value.rows, linear / value.rows); } template -double sparse_subscript_element(const sparse_matrix& value, - const RowSelector& row_selector, - const ColumnSelector& column_selector, - const std::size_t base = 1U) { +T sparse_subscript_element(const sparse_matrix& value, + const RowSelector& row_selector, + const ColumnSelector& column_selector, + const std::size_t base = 1U) { validate_sparse_csc(value, "subscript operand"); if (value.rows == 0U || value.columns == 0U) throw std::out_of_range( @@ -880,7 +966,7 @@ double sparse_subscript_element(const sparse_matrix& value, throw std::invalid_argument("MPF Matlab sparse scalar subscript selected multiple elements"); return sparse_value_at(value, rows.front(), columns.front()); } -template sparse_matrix sparse_linear_selection( +template sparse_matrix sparse_linear_selection( const sparse_matrix& value, const Selector& selector, const std::optional planned_rows, const std::optional planned_columns, const std::size_t base = 1U) { @@ -891,7 +977,7 @@ template sparse_matrix sparse_linear_sel const auto [rows, columns] = sparse_linear_result_shape(planned_rows, planned_columns, size); if (rows != size || columns != 1U) throw std::invalid_argument("MPF Matlab sparse full-colon result shape is inconsistent"); - sparse_matrix result; + sparse_matrix result; result.rows = rows; result.columns = columns; result.column_pointers = {0U, value.values.size()}; result.row_indices.reserve(value.row_indices.size()); @@ -901,7 +987,7 @@ template sparse_matrix sparse_linear_sel for (auto stored = value.column_pointers[column]; stored < value.column_pointers[column + 1U]; ++stored) { result.row_indices.push_back(offset + value.row_indices[stored]); - result.values.push_back(static_cast(value.values[stored])); + result.values.push_back(value.values[stored]); } } validate_sparse_csc(result, "full-colon selection result"); @@ -910,14 +996,14 @@ template sparse_matrix sparse_linear_sel const auto indices = selector_indices(size, resolved, base, false); const auto [rows, columns] = sparse_linear_result_shape(planned_rows, planned_columns, indices.size()); - sparse_matrix result; + sparse_matrix result; result.rows = rows; result.columns = columns; result.column_pointers.push_back(0U); result.row_indices.reserve(indices.size()); result.values.reserve(indices.size()); for (std::size_t column = 0U; column < columns; ++column) { for (std::size_t row = 0U; row < rows; ++row) { const auto linear = indices[row + column * rows]; const auto stored = sparse_value_at(value, linear % value.rows, linear / value.rows); - if (stored != 0.0) { result.row_indices.push_back(row); result.values.push_back(stored); } + if (stored != T{}) { result.row_indices.push_back(row); result.values.push_back(stored); } } result.column_pointers.push_back(result.values.size()); } @@ -925,7 +1011,7 @@ template sparse_matrix sparse_linear_sel return result; } template -sparse_matrix sparse_submatrix_selection( +sparse_matrix sparse_submatrix_selection( const sparse_matrix& value, const RowSelector& row_selector, const ColumnSelector& column_selector, const std::optional planned_rows, const std::optional planned_columns, const std::size_t base = 1U) { @@ -941,7 +1027,7 @@ sparse_matrix sparse_submatrix_selection( for (std::size_t output_row = 0U; output_row < selected_rows.size(); ++output_row) row_map.emplace_back(selected_rows[output_row], output_row); std::sort(row_map.begin(), row_map.end()); - sparse_matrix result; + sparse_matrix result; result.rows = rows; result.columns = columns; result.column_pointers.push_back(0U); const auto capacity = sparse_element_count(rows, columns); const auto sparse_capacity = std::min(capacity, value.values.size()); @@ -951,13 +1037,13 @@ sparse_matrix sparse_submatrix_selection( auto selected = row_map.begin(); auto stored = value.column_pointers[source_column]; const auto stored_end = value.column_pointers[source_column + 1U]; - std::vector> entries; + std::vector> entries; while (selected != row_map.end() && stored < stored_end) { const auto selected_row = selected->first; const auto stored_row = value.row_indices[stored]; if (selected_row < stored_row) { ++selected; continue; } if (stored_row < selected_row) { ++stored; continue; } - const auto stored_value = static_cast(value.values[stored++]); + const auto stored_value = static_cast(value.values[stored++]); while (selected != row_map.end() && selected->first == selected_row) { entries.emplace_back(selected->second, stored_value); ++selected; @@ -1004,7 +1090,7 @@ template sparse_replacement_payload sparse_replacement_va for (const auto value : result.values) if (!std::isfinite(value)) throw std::invalid_argument( - "MPF Matlab sparse assignment requires finite real values"); + "MPF Matlab sparse assignment requires finite real or logical values"); return result; } inline std::vector sparse_nonsingleton_shape( diff --git a/src/backends/javascript/lir.hpp b/src/backends/javascript/lir.hpp index 1393eac..e4e9264 100644 --- a/src/backends/javascript/lir.hpp +++ b/src/backends/javascript/lir.hpp @@ -265,6 +265,8 @@ struct SparseConstructionPlan { std::vector result_shape; std::vector triplet_element_counts; std::size_t reserve_hint{0U}; + semantic::SparseValueDomain value_domain{semantic::SparseValueDomain::none}; + semantic::SparseDuplicatePolicy duplicate_policy{semantic::SparseDuplicatePolicy::none}; [[nodiscard]] bool valid() const noexcept { return kind != semantic::SparseConstructionKind::none; @@ -327,6 +329,7 @@ struct ExpressionPlan { BroadcastPlan broadcast; SparseElementwisePlan sparse_elementwise; std::vector> runtime_shape_arguments; + std::vector runtime_integer_arguments; ReductionPlan reduction; SparseIndexPlan sparse_index; SparseReshapePlan sparse_reshape; diff --git a/src/backends/javascript/lir_representation.cpp b/src/backends/javascript/lir_representation.cpp index 1396a5d..20bceea 100644 --- a/src/backends/javascript/lir_representation.cpp +++ b/src/backends/javascript/lir_representation.cpp @@ -438,7 +438,8 @@ bool valid_sparse_elementwise_plan(const lir::Expression& expression) noexcept { std::optional sparse_argument_count(const lir::Expression& expression) noexcept { if (expression.inferred_type != ValueType::list) { - return expression.inferred_type == ValueType::integer || + return expression.inferred_type == ValueType::boolean || + expression.inferred_type == ValueType::integer || expression.inferred_type == ValueType::real ? std::optional{1U} : std::nullopt; @@ -454,6 +455,22 @@ std::optional sparse_argument_count(const lir::Expression& expressi return count; } +semantic::SparseValueDomain sparse_value_domain(const lir::Expression& expression) noexcept { + const auto type = expression.inferred_type == ValueType::list ? expression.element_type + : expression.inferred_type; + const auto numeric_type = expression.inferred_type == ValueType::list + ? expression.element_numeric_type + : expression.numeric_type; + if (type == ValueType::boolean && numeric_type == logical_numeric_type) { + return semantic::SparseValueDomain::logical; + } + if ((type == ValueType::integer || type == ValueType::real) && + numeric_type.complexity == NumericComplexity::real) { + return semantic::SparseValueDomain::finite_real; + } + return semantic::SparseValueDomain::none; +} + semantic::SparseConstructionKind expected_sparse_construction_kind( const lir::Expression& expression) noexcept { if (expression.kind != ExpressionKind::call || expression.children.empty()) { @@ -478,11 +495,15 @@ bool valid_sparse_construction(const lir::Expression& expression) noexcept { if (sparse.kind != expected_sparse_construction_kind(expression)) return false; if (!sparse.valid()) { return sparse.result_shape.empty() && sparse.triplet_element_counts.empty() && - sparse.reserve_hint == 0U; + sparse.reserve_hint == 0U && sparse.value_domain == semantic::SparseValueDomain::none && + sparse.duplicate_policy == semantic::SparseDuplicatePolicy::none; } bool valid = expression.inferred_type == ValueType::list && expression.array_storage == ArrayStorageFormat::sparse_csc && sparse.result_shape == expression.shape && sparse.result_shape.size() == 2U && + sparse.value_domain == sparse_value_domain(expression) && + semantic::valid_sparse_construction_value_contract(sparse.kind, sparse.value_domain, + sparse.duplicate_policy) && std::none_of(sparse.result_shape.begin(), sparse.result_shape.end(), [](const auto extent) { return extent == dynamic_extent; }); const bool triplets = sparse.kind == semantic::SparseConstructionKind::triplets_inferred || @@ -506,7 +527,11 @@ bool valid_sparse_construction(const lir::Expression& expression) noexcept { } if (sparse.kind == semantic::SparseConstructionKind::dense_conversion) { valid = valid && expression.children.size() == 2U && - expression.children[1].shape == sparse.result_shape; + expression.children[1].shape == sparse.result_shape && + sparse.value_domain == sparse_value_domain(expression.children[1]); + } else if (triplets) { + valid = valid && expression.children.size() >= 4U && + sparse.value_domain == sparse_value_domain(expression.children[3]); } return valid && (sparse.kind == semantic::SparseConstructionKind::triplets_reserved || sparse.reserve_hint == 0U); @@ -540,13 +565,14 @@ bool valid_sparse_reshape(const lir::Expression& expression) noexcept { const auto expected_inference = empty_dimensions == 1U ? semantic::SparseReshapeInference::one_dimension : semantic::SparseReshapeInference::none; - return source.inferred_type == ValueType::list && source.element_type == ValueType::real && - source.element_numeric_type == real_numeric_type && + return source.inferred_type == ValueType::list && + semantic::valid_sparse_stored_value_type(source.element_type, + source.element_numeric_type) && expression.inferred_type == ValueType::list && - expression.element_type == ValueType::real && - expression.element_numeric_type == real_numeric_type && expression.column_major && - sparse.dimension_form == expected_form && empty_dimensions <= 1U && - sparse.inference == expected_inference && + expression.element_type == source.element_type && + expression.element_numeric_type == source.element_numeric_type && + expression.column_major && sparse.dimension_form == expected_form && + empty_dimensions <= 1U && sparse.inference == expected_inference && (expected_inference == semantic::SparseReshapeInference::none || sparse.inferred_axis == empty_axis) && sparse.input_shape == source.shape && sparse.source_storage == source.array_storage && @@ -902,6 +928,16 @@ lir::ExpressionPlan expected_expression_plan(const lir::Expression& expression, semantic::SparseConstructionKind::dense_conversion) { result.runtime_shape_arguments = {expression.sparse_construction.result_shape}; } + if (result.call == lir::CallForm::matlab_sparse) { + const auto dense = !result.runtime_shape_arguments.empty(); + result.token = dense ? "__mpf_sparse_from_dense" : "__mpf_sparse"; + result.runtime_integer_arguments = { + static_cast(expression.sparse_construction.value_domain)}; + if (!dense) { + result.runtime_integer_arguments.push_back( + static_cast(expression.sparse_construction.duplicate_policy)); + } + } result.call_value = expression.multi_output_call && expression.requested_outputs == 1 ? lir::CallValueForm::first_result : lir::CallValueForm::direct; @@ -1024,6 +1060,7 @@ bool same_plan(const lir::ExpressionPlan& left, const lir::ExpressionPlan& right left.sparse_elementwise.result_shape != right.sparse_elementwise.result_shape || left.sparse_elementwise.axes != right.sparse_elementwise.axes || left.runtime_shape_arguments != right.runtime_shape_arguments || + left.runtime_integer_arguments != right.runtime_integer_arguments || left.reduction.operation != right.reduction.operation || left.reduction.axis_policy != right.reduction.axis_policy || left.reduction.shape_source != right.reduction.shape_source || @@ -1181,8 +1218,8 @@ void verify_expression(const lir::Expression& expression, const lir::EmissionPla } else if (expression.sparse_index.valid()) { const auto& source = expression.children.front(); const auto scalar = semantic::sparse_index_returns_scalar(expression.sparse_index.kind); - if (source.element_type != ValueType::real || - source.element_numeric_type != real_numeric_type || + if (!semantic::valid_sparse_stored_value_type(source.element_type, + source.element_numeric_type) || expression.sparse_index.input_shape != source.shape || expression.sparse_index.source_storage != source.array_storage || expression.sparse_index.result_shape != expression.shape || diff --git a/src/backends/javascript/renderer.cpp b/src/backends/javascript/renderer.cpp index 5053dd5..0784b96 100644 --- a/src/backends/javascript/renderer.cpp +++ b/src/backends/javascript/renderer.cpp @@ -280,18 +280,25 @@ class Renderer final { return; case javascript::lir::CallForm::matlab_sparse: if (!plan.runtime_shape_arguments.empty()) { - output_ << "__mpf_sparse_from_dense("; + output_ << plan.token << '('; emit_expression(expression.children[1]); for (const auto& shape : plan.runtime_shape_arguments) { output_ << ", "; emit_shape(shape); } + for (const auto argument : plan.runtime_integer_arguments) { + output_ << ", " << argument; + } output_ << ')'; return; } - output_ << "__mpf_sparse("; + output_ << plan.token << '('; + for (std::size_t index = 0U; index < plan.runtime_integer_arguments.size(); ++index) { + if (index != 0U) output_ << ", "; + output_ << plan.runtime_integer_arguments[index]; + } for (std::size_t index = 1U; index < expression.children.size(); ++index) { - if (index != 1U) output_ << ", "; + if (!plan.runtime_integer_arguments.empty() || index != 1U) output_ << ", "; emit_expression(expression.children[index]); } output_ << ')'; diff --git a/src/backends/javascript/sparse_matrix_runtime.cpp b/src/backends/javascript/sparse_matrix_runtime.cpp index 3c73780..90fed2a 100644 --- a/src/backends/javascript/sparse_matrix_runtime.cpp +++ b/src/backends/javascript/sparse_matrix_runtime.cpp @@ -6,13 +6,20 @@ namespace mpf::detail { void emit_javascript_sparse_matrix_runtime(std::ostream& output) { output << R"MPF(const __mpf_sparse_csc_tag = Symbol('mpf.sparse.csc'); +const __mpf_sparse_value_finite_real = 1; +const __mpf_sparse_value_logical = 2; +const __mpf_sparse_duplicate_none = 0; +const __mpf_sparse_duplicate_sum = 1; +const __mpf_sparse_duplicate_logical_any = 2; function __mpf_issparse(value) { return value !== null && typeof value === 'object' && value[__mpf_sparse_csc_tag] === true; } function __mpf_validate_sparse_csc(value, name = 'sparse matrix') { if (!__mpf_issparse(value)) throw new TypeError(`MPF Matlab ${name} is not a CSC matrix`); - const { rows, columns, columnPointers, rowIndices, values } = value; + const { rows, columns, columnPointers, rowIndices, values, valueDomain } = value; if (!Number.isSafeInteger(rows) || rows < 0 || !Number.isSafeInteger(columns) || columns < 0 || + (valueDomain !== __mpf_sparse_value_finite_real && + valueDomain !== __mpf_sparse_value_logical) || !Array.isArray(columnPointers) || columnPointers.length !== columns + 1 || !Array.isArray(rowIndices) || !Array.isArray(values) || rowIndices.length !== values.length || columnPointers[0] !== 0 || columnPointers[columns] !== values.length) { @@ -24,22 +31,28 @@ function __mpf_validate_sparse_csc(value, name = 'sparse matrix') { end > values.length) throw new RangeError(`MPF Matlab ${name} has invalid CSC pointers`); let previous = -1; for (let index = begin; index < end; ++index) { - const row = rowIndices[index]; const numeric = values[index]; + const row = rowIndices[index]; const stored = values[index]; + const validValue = valueDomain === __mpf_sparse_value_logical + ? stored === true + : typeof stored === 'number' && Number.isFinite(stored) && stored !== 0; if (!Number.isSafeInteger(row) || row <= previous || row >= rows || - typeof numeric !== 'number' || !Number.isFinite(numeric) || numeric === 0) { - throw new RangeError(`MPF Matlab ${name} is not canonical finite-real CSC`); + !validValue) { + throw new RangeError(`MPF Matlab ${name} is not canonical finite-real/logical CSC`); } previous = row; } } return value; } -function __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values) { +function __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values, + valueDomain = __mpf_sparse_value_finite_real) { return __mpf_validate_sparse_csc({ - rows, columns, columnPointers, rowIndices, values, [__mpf_sparse_csc_tag]: true + rows, columns, columnPointers, rowIndices, values, valueDomain, + [__mpf_sparse_csc_tag]: true }); } -function __mpf_sparse_dense_rank2(value, name, plannedShape = undefined) { +function __mpf_sparse_dense_rank2(value, name, plannedShape = undefined, + valueDomain = __mpf_sparse_value_finite_real) { const shape = __mpf_matlab_runtime_shape(value, name); if (shape.length !== 2) throw new RangeError(`MPF Matlab ${name} must be rank 2`); if (plannedShape !== undefined && @@ -50,13 +63,20 @@ function __mpf_sparse_dense_rank2(value, name, plannedShape = undefined) { } const flattened = __mpf_flatten_column_major(value); for (const item of flattened) { - if (typeof item !== 'number' || !Number.isFinite(item)) { - throw new TypeError(`MPF Matlab ${name} requires finite real values`); + const valid = valueDomain === __mpf_sparse_value_logical + ? typeof item === 'boolean' + : typeof item === 'number' && Number.isFinite(item); + if (!valid) { + throw new TypeError(`MPF Matlab ${name} disagrees with its sparse value-domain plan`); } } return { shape, flattened }; } -function __mpf_sparse_from_dense(value, plannedShape) { +function __mpf_sparse_from_dense(value, plannedShape, valueDomain) { + if (valueDomain !== __mpf_sparse_value_finite_real && + valueDomain !== __mpf_sparse_value_logical) { + throw new RangeError('MPF Matlab sparse conversion has an invalid value-domain plan'); + } if (plannedShape !== undefined && (!Array.isArray(plannedShape) || plannedShape.length !== 2 || plannedShape.some(extent => !Number.isSafeInteger(extent) || extent < 0))) { @@ -64,13 +84,17 @@ function __mpf_sparse_from_dense(value, plannedShape) { } if (__mpf_issparse(value)) { const matrix = __mpf_validate_sparse_csc(value); + if (matrix.valueDomain !== valueDomain) { + throw new TypeError('MPF Matlab sparse input disagrees with its value-domain plan'); + } if (plannedShape !== undefined && (matrix.rows !== plannedShape[0] || matrix.columns !== plannedShape[1])) { throw new RangeError('MPF Matlab sparse input disagrees with its static shape contract'); } return matrix; } - const { shape, flattened } = __mpf_sparse_dense_rank2(value, 'sparse input'); + const { shape, flattened } = __mpf_sparse_dense_rank2( + value, 'sparse input', undefined, valueDomain); if (plannedShape !== undefined && (shape[0] !== plannedShape[0] || shape[1] !== plannedShape[1])) { throw new RangeError('MPF Matlab sparse input disagrees with its static shape contract'); @@ -80,11 +104,13 @@ function __mpf_sparse_from_dense(value, plannedShape) { for (let column = 0; column < columns; ++column) { for (let row = 0; row < rows; ++row) { const numeric = flattened[row + column * rows]; - if (numeric !== 0) { rowIndices.push(row); values.push(numeric); } + const keep = valueDomain === __mpf_sparse_value_logical ? numeric : numeric !== 0; + if (keep) { rowIndices.push(row); values.push(numeric); } } columnPointers.push(values.length); } - return __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values); + return __mpf_make_sparse_csc( + rows, columns, columnPointers, rowIndices, values, valueDomain); } function __mpf_sparse_dimension(value, name, allowZero = false) { if (typeof value !== 'number' || !Number.isSafeInteger(value) || @@ -93,19 +119,34 @@ function __mpf_sparse_dimension(value, name, allowZero = false) { } return value; } -function __mpf_sparse_triplet_input(value, name) { +function __mpf_sparse_triplet_input(value, name, valueDomain, storedValues = false) { const sequence = Array.isArray(value); - const flattened = sequence ? __mpf_flatten_column_major(value) : [value]; - for (const item of flattened) if (typeof item !== 'number' || !Number.isFinite(item)) { - throw new TypeError(`MPF Matlab sparse ${name} requires finite real values`); + const source = sequence ? __mpf_flatten_column_major(value) : [value]; + const flattened = []; + for (const item of source) { + const logicalStored = storedValues && valueDomain === __mpf_sparse_value_logical; + if (logicalStored ? typeof item !== 'boolean' + : (typeof item !== 'number' && typeof item !== 'boolean') || + !Number.isFinite(Number(item))) { + throw new TypeError(`MPF Matlab sparse ${name} disagrees with its value-domain plan`); + } + flattened.push(logicalStored ? item : Number(item)); } return { sequence, flattened }; } function __mpf_sparse_from_triplets(rowValue, columnValue, storedValue, - explicitRows, explicitColumns, reserveHint) { - const rowInput = __mpf_sparse_triplet_input(rowValue, 'row indices'); - const columnInput = __mpf_sparse_triplet_input(columnValue, 'column indices'); - const valueInput = __mpf_sparse_triplet_input(storedValue, 'stored values'); + explicitRows, explicitColumns, reserveHint, + valueDomain, duplicatePolicy) { + const expectedPolicy = valueDomain === __mpf_sparse_value_logical + ? __mpf_sparse_duplicate_logical_any : __mpf_sparse_duplicate_sum; + if ((valueDomain !== __mpf_sparse_value_finite_real && + valueDomain !== __mpf_sparse_value_logical) || duplicatePolicy !== expectedPolicy) { + throw new RangeError('MPF Matlab sparse triplet value-domain plan is inconsistent'); + } + const rowInput = __mpf_sparse_triplet_input(rowValue, 'row indices', valueDomain); + const columnInput = __mpf_sparse_triplet_input(columnValue, 'column indices', valueDomain); + const valueInput = __mpf_sparse_triplet_input( + storedValue, 'stored values', valueDomain, true); const inputs = [rowInput, columnInput, valueInput]; let sequenceCount; for (const input of inputs) if (input.sequence) { @@ -141,31 +182,55 @@ function __mpf_sparse_from_triplets(rowValue, columnValue, storedValue, let entry = 0; for (let column = 0; column < columns; ++column) { while (entry < entries.length && entries[entry].column === column) { - const row = entries[entry].row; let sum = 0; + const row = entries[entry].row; let sum = 0; let any = false; while (entry < entries.length && entries[entry].column === column && - entries[entry].row === row) sum += entries[entry++].value; - if (!Number.isFinite(sum)) { + entries[entry].row === row) { + const stored = entries[entry++].value; + if (valueDomain === __mpf_sparse_value_logical) any = any || stored; + else sum += stored; + } + if (valueDomain === __mpf_sparse_value_finite_real && !Number.isFinite(sum)) { throw new RangeError('MPF Matlab sparse duplicate accumulation is not finite'); } - if (sum !== 0) { rowIndices.push(row); values.push(sum); } + if (valueDomain === __mpf_sparse_value_logical ? any : sum !== 0) { + rowIndices.push(row); values.push(valueDomain === __mpf_sparse_value_logical ? true : sum); + } } columnPointers.push(values.length); } - return __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values); + return __mpf_make_sparse_csc( + rows, columns, columnPointers, rowIndices, values, valueDomain); } -function __mpf_sparse(...args) { - if (args.length === 1) return __mpf_sparse_from_dense(args[0]); +function __mpf_sparse(valueDomain, duplicatePolicy, ...args) { + if (args.length === 1) { + if (duplicatePolicy !== __mpf_sparse_duplicate_none) { + throw new RangeError('MPF Matlab sparse conversion has an invalid duplicate policy'); + } + return __mpf_sparse_from_dense(args[0], undefined, valueDomain); + } if (args.length === 2) { + if (valueDomain !== __mpf_sparse_value_finite_real || + duplicatePolicy !== __mpf_sparse_duplicate_none) { + throw new RangeError('MPF Matlab sparse zero matrix has an invalid value-domain plan'); + } const rows = __mpf_sparse_dimension(args[0], 'row extent', true); const columns = __mpf_sparse_dimension(args[1], 'column extent', true); return __mpf_make_sparse_csc(rows, columns, new Array(columns + 1).fill(0), [], []); } - if (args.length === 3) return __mpf_sparse_from_triplets(args[0], args[1], args[2]); + if (args.length === 3) { + return __mpf_sparse_from_triplets( + args[0], args[1], args[2], undefined, undefined, undefined, + valueDomain, duplicatePolicy); + } if (args.length === 5) { - return __mpf_sparse_from_triplets(args[0], args[1], args[2], args[3], args[4]); + return __mpf_sparse_from_triplets( + args[0], args[1], args[2], args[3], args[4], undefined, + valueDomain, duplicatePolicy); } if (args.length === 6) { - return __mpf_sparse_from_triplets(args[0], args[1], args[2], args[3], args[4], args[5]); + return __mpf_sparse_from_triplets( + args[0], args[1], args[2], args[3], args[4], args[5], + valueDomain, duplicatePolicy); } throw new TypeError('MPF Matlab sparse received an unsupported argument contract'); } @@ -175,7 +240,8 @@ function __mpf_full(value) { return value; } const matrix = __mpf_validate_sparse_csc(value); - const flattened = new Array(matrix.rows * matrix.columns).fill(0); + const flattened = new Array(matrix.rows * matrix.columns).fill( + matrix.valueDomain === __mpf_sparse_value_logical ? false : 0); for (let column = 0; column < matrix.columns; ++column) { for (let index = matrix.columnPointers[column]; index < matrix.columnPointers[column + 1]; ++index) { @@ -532,7 +598,8 @@ function __mpf_sparse_transpose(value) { rows[target] = column; values[target] = matrix.values[index]; } } - return __mpf_make_sparse_csc(matrix.columns, matrix.rows, pointers, rows, values); + return __mpf_make_sparse_csc( + matrix.columns, matrix.rows, pointers, rows, values, matrix.valueDomain); } function __mpf_sparse_reshape_shape(shape, name, minimumRank) { if (!Array.isArray(shape) || shape.length < minimumRank) { @@ -586,7 +653,8 @@ function __mpf_sparse_reshape(value, inputShape, requestedShape, resultShape) { for (let column = 0; column < columns; ++column) { columnPointers[column + 1] += columnPointers[column]; } - return __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values); + return __mpf_make_sparse_csc( + rows, columns, columnPointers, rowIndices, values, matrix.valueDomain); } function __mpf_sparse_value_at(matrix, row, column) { let first = matrix.columnPointers[column]; let last = matrix.columnPointers[column + 1]; @@ -596,7 +664,8 @@ function __mpf_sparse_value_at(matrix, row, column) { if (candidate < row) first = middle + 1; else last = middle; } return first < matrix.columnPointers[column + 1] && matrix.rowIndices[first] === row - ? matrix.values[first] : 0; + ? matrix.values[first] + : matrix.valueDomain === __mpf_sparse_value_logical ? false : 0; } function __mpf_sparse_linear_shape(shape, count) { if (!Array.isArray(shape) || shape.length !== 2) { @@ -693,7 +762,8 @@ function __mpf_sparse_linear_selection(value, selector, resultShape) { } } return __mpf_make_sparse_csc( - rows, columns, [0, matrix.values.length], rowIndices, matrix.values.slice()); + rows, columns, [0, matrix.values.length], rowIndices, matrix.values.slice(), + matrix.valueDomain); } const indices = __mpf_selector_indices(size, resolvedSelector, 1, false); const [rows, columns] = __mpf_sparse_linear_shape(resultShape, indices.length); @@ -707,7 +777,8 @@ function __mpf_sparse_linear_selection(value, selector, resultShape) { } pointers.push(values.length); } - return __mpf_make_sparse_csc(rows, columns, pointers, rowIndices, values); + return __mpf_make_sparse_csc( + rows, columns, pointers, rowIndices, values, matrix.valueDomain); } function __mpf_sparse_submatrix_selection(value, rowSelector, columnSelector, resultShape) { const matrix = __mpf_validate_sparse_csc(value, 'submatrix operand'); @@ -738,7 +809,8 @@ function __mpf_sparse_submatrix_selection(value, rowSelector, columnSelector, re for (const entry of entries) { rowIndices.push(entry.row); values.push(entry.value); } pointers.push(values.length); } - return __mpf_make_sparse_csc(rows, columns, pointers, rowIndices, values); + return __mpf_make_sparse_csc( + rows, columns, pointers, rowIndices, values, matrix.valueDomain); } function __mpf_sparse_nonsingleton_shape(shape) { return shape.filter((extent) => extent !== 1); @@ -746,41 +818,42 @@ function __mpf_sparse_nonsingleton_shape(shape) { function __mpf_sparse_same_shape(left, right) { return left.length === right.length && left.every((extent, axis) => extent === right[axis]); } -function __mpf_sparse_replacement_payload(value) { +function __mpf_sparse_replacement_value(value, valueDomain) { + if ((typeof value !== 'number' && typeof value !== 'boolean') || + (typeof value === 'number' && !Number.isFinite(value))) { + throw new TypeError('MPF Matlab sparse assignment requires finite real or logical values'); + } + return valueDomain === __mpf_sparse_value_logical ? Number(value) !== 0 : Number(value); +} +function __mpf_sparse_replacement_payload(value, valueDomain) { if (__mpf_issparse(value)) { const matrix = __mpf_validate_sparse_csc(value, 'assignment replacement'); const size = matrix.rows * matrix.columns; if (!Number.isSafeInteger(size)) { throw new RangeError('MPF Matlab sparse replacement exceeds safe integer limits'); } - const flattened = new Array(size).fill(0); + const flattened = new Array(size).fill( + valueDomain === __mpf_sparse_value_logical ? false : 0); for (let column = 0; column < matrix.columns; ++column) { for (let stored = matrix.columnPointers[column]; stored < matrix.columnPointers[column + 1]; ++stored) { - flattened[matrix.rowIndices[stored] + column * matrix.rows] = matrix.values[stored]; + flattened[matrix.rowIndices[stored] + column * matrix.rows] = + __mpf_sparse_replacement_value(matrix.values[stored], valueDomain); } } return { shape: [matrix.rows, matrix.columns], flattened }; } if (Array.isArray(value)) { const shape = __mpf_matlab_runtime_shape(value, 'sparse assignment replacement'); - const flattened = __mpf_flatten_column_major(value).map((item) => { - if ((typeof item !== 'number' && typeof item !== 'boolean') || - (typeof item === 'number' && !Number.isFinite(item))) { - throw new TypeError('MPF Matlab sparse assignment requires finite real values'); - } - return Number(item); - }); + const flattened = __mpf_flatten_column_major(value).map( + (item) => __mpf_sparse_replacement_value(item, valueDomain)); return { shape, flattened }; } - if ((typeof value !== 'number' && typeof value !== 'boolean') || - (typeof value === 'number' && !Number.isFinite(value))) { - throw new TypeError('MPF Matlab sparse assignment requires finite real values'); - } - return { shape: [], flattened: [Number(value)] }; + return { shape: [], flattened: [__mpf_sparse_replacement_value(value, valueDomain)] }; } -function __mpf_sparse_assignment_payload(value, scalarExpansion, selectionShape, count) { - const payload = __mpf_sparse_replacement_payload(value); +function __mpf_sparse_assignment_payload(value, scalarExpansion, selectionShape, count, + valueDomain) { + const payload = __mpf_sparse_replacement_payload(value, valueDomain); if (scalarExpansion) { if (payload.flattened.length !== 1) { throw new RangeError('MPF Matlab sparse scalar expansion requires one replacement value'); @@ -800,10 +873,11 @@ function __mpf_sparse_verify_result_shape(planned, actual, operation) { } } function __mpf_sparse_commit(value, rows, columns, columnPointers, rowIndices, values) { - const result = __mpf_make_sparse_csc(rows, columns, columnPointers, rowIndices, values); + const result = __mpf_make_sparse_csc( + rows, columns, columnPointers, rowIndices, values, value.valueDomain); value.rows = result.rows; value.columns = result.columns; value.columnPointers = result.columnPointers; value.rowIndices = result.rowIndices; - value.values = result.values; + value.values = result.values; value.valueDomain = result.valueDomain; return value; } function __mpf_sparse_assign(value, selectors, replacement, base, linear, scalarExpansion, @@ -854,7 +928,7 @@ function __mpf_sparse_assign(value, selectors, replacement, base, linear, scalar } __mpf_sparse_verify_result_shape(plannedResultShape, [rows, columns], 'assignment'); const payload = __mpf_sparse_assignment_payload( - replacement, scalarExpansion, selectionShape, coordinates.length); + replacement, scalarExpansion, selectionShape, coordinates.length, matrix.valueDomain); const updates = coordinates.map((coordinate, sequence) => ({ ...coordinate, sequence, value: payload.values[payload.scalar ? 0 : sequence] })); @@ -882,10 +956,14 @@ function __mpf_sparse_assign(value, selectors, replacement, base, linear, scalar if (storedRow < changedRow) { rowIndices.push(storedRow); values.push(matrix.values[stored++]); } else if (changedRow < storedRow) { - if (changed.value !== 0) { rowIndices.push(changedRow); values.push(changed.value); } + const keep = matrix.valueDomain === __mpf_sparse_value_logical + ? changed.value : changed.value !== 0; + if (keep) { rowIndices.push(changedRow); values.push(changed.value); } ++update; } else { - if (changed.value !== 0) { rowIndices.push(changedRow); values.push(changed.value); } + const keep = matrix.valueDomain === __mpf_sparse_value_logical + ? changed.value : changed.value !== 0; + if (keep) { rowIndices.push(changedRow); values.push(changed.value); } ++stored; ++update; } } @@ -1156,7 +1234,7 @@ function __mpf_matlab_mldivide_sparse_real_square( __mpf_matlab_warn_square_condition( __mpf_sparse_rcond(matrix, factor, apply, applyTranspose)); const result = apply(factor, right); - return preserveSparse ? __mpf_sparse_from_dense(result, resultShape) : result; + return preserveSparse ? __mpf_sparse_from_dense(result, resultShape, 1) : result; } function __mpf_matlab_mrdivide_sparse_real_square( leftValue, coefficients, leftShape, rightShape, resultShape) { diff --git a/src/ir/dump.cpp b/src/ir/dump.cpp index eafa81c..f9e9259 100644 --- a/src/ir/dump.cpp +++ b/src/ir/dump.cpp @@ -169,7 +169,7 @@ std::string dump_normalized_hir(const hir::Program& program) { std::string dump_semantics(const hir::SemanticTable& table) { std::ostringstream output; - output << "semantic-v20 hir-nodes=" << table.hir_node_count + output << "semantic-v22 hir-nodes=" << table.hir_node_count << " hir-revision=" << table.hir_revision << " expressions=" << table.expressions.size() << " statements=" << table.statements.size() << '\n'; for (std::size_t id = 1; id < table.nodes.size(); ++id) { @@ -313,7 +313,9 @@ std::string dump_semantics(const hir::SemanticTable& table) { if (index != 0U) output << ','; output << facts.sparse_construction.triplet_element_counts[index]; } - output << "] reserve=" << facts.sparse_construction.reserve_hint; + output << "] reserve=" << facts.sparse_construction.reserve_hint + << " value-domain=" << enum_value(facts.sparse_construction.value_domain) + << " duplicate-policy=" << enum_value(facts.sparse_construction.duplicate_policy); } if (facts.sparse_index.valid()) { output << " sparse-index=" << enum_value(facts.sparse_index.kind) << " input=["; @@ -388,7 +390,7 @@ std::string dump_semantics(const hir::SemanticTable& table) { std::string dump_mir(const mir::Program& program) { std::ostringstream output; - output << "mir-v26 language=" << enum_value(program.source_language) + output << "mir-v28 language=" << enum_value(program.source_language) << " hir-nodes=" << program.hir_node_count << " expressions=" << (program.expressions.empty() ? 0U : program.expressions.size() - 1U) << " operations=" << (program.statements.empty() ? 0U : program.statements.size() - 1U) @@ -528,7 +530,10 @@ std::string dump_mir(const mir::Program& program) { if (count_index != 0U) output << ','; output << attributes->sparse_construction.triplet_element_counts[count_index]; } - output << "] reserve=" << attributes->sparse_construction.reserve_hint; + output << "] reserve=" << attributes->sparse_construction.reserve_hint + << " value-domain=" << enum_value(attributes->sparse_construction.value_domain) + << " duplicate-policy=" + << enum_value(attributes->sparse_construction.duplicate_policy); } if (attributes->sparse_index.valid()) { output << " sparse-index=" << enum_value(attributes->sparse_index.kind) << " input=!s" diff --git a/src/ir/mir.cpp b/src/ir/mir.cpp index e982dd4..0249d59 100644 --- a/src/ir/mir.cpp +++ b/src/ir/mir.cpp @@ -916,6 +916,10 @@ class Builder final { semantic_facts->sparse_construction.triplet_element_counts; result_attributes.sparse_construction.reserve_hint = semantic_facts->sparse_construction.reserve_hint; + result_attributes.sparse_construction.value_domain = + semantic_facts->sparse_construction.value_domain; + result_attributes.sparse_construction.duplicate_policy = + semantic_facts->sparse_construction.duplicate_policy; } if (semantic_facts->sparse_index.valid()) { result_attributes.sparse_index.kind = semantic_facts->sparse_index.kind; diff --git a/src/ir/mir.hpp b/src/ir/mir.hpp index 6855905..1eea5d7 100644 --- a/src/ir/mir.hpp +++ b/src/ir/mir.hpp @@ -363,6 +363,8 @@ struct SparseConstructionPlan { ShapeId result_shape{}; std::vector triplet_element_counts; std::size_t reserve_hint{0U}; + semantic::SparseValueDomain value_domain{semantic::SparseValueDomain::none}; + semantic::SparseDuplicatePolicy duplicate_policy{semantic::SparseDuplicatePolicy::none}; [[nodiscard]] bool valid() const noexcept { return kind != semantic::SparseConstructionKind::none; diff --git a/src/ir/mir_verifier.cpp b/src/ir/mir_verifier.cpp index ab6fd4c..4925c5f 100644 --- a/src/ir/mir_verifier.cpp +++ b/src/ir/mir_verifier.cpp @@ -90,8 +90,9 @@ std::optional sparse_argument_count(const Program& program, const Expression& expression) noexcept { if (value_type(program, expression.type_id) != ValueType::list) { const auto type = value_type(program, expression.type_id); - return type == ValueType::integer || type == ValueType::real ? std::optional{1U} - : std::nullopt; + return type == ValueType::boolean || type == ValueType::integer || type == ValueType::real + ? std::optional{1U} + : std::nullopt; } const auto* argument_shape = shape(program, expression.shape_id); if (argument_shape == nullptr) return std::nullopt; @@ -106,6 +107,24 @@ std::optional sparse_argument_count(const Program& program, return count; } +semantic::SparseValueDomain sparse_value_domain(const Program& program, + const Expression& expression) noexcept { + const auto type = value_type(program, expression.type_id) == ValueType::list + ? element_type(program, expression.type_id) + : value_type(program, expression.type_id); + const auto numeric_type = value_type(program, expression.type_id) == ValueType::list + ? element_numeric_type(program, expression.type_id) + : mir::numeric_type(program, expression.type_id); + if (type == ValueType::boolean && numeric_type == logical_numeric_type) { + return semantic::SparseValueDomain::logical; + } + if ((type == ValueType::integer || type == ValueType::real) && + numeric_type.complexity == NumericComplexity::real) { + return semantic::SparseValueDomain::finite_real; + } + return semantic::SparseValueDomain::none; +} + semantic::SparseConstructionKind expected_sparse_construction_kind( const Program& program, const Expression& expression) noexcept { if (expression.kind != ExpressionKind::call || expression.children.empty()) { @@ -653,6 +672,9 @@ void verify_expression(const Expression& expression, const Program& program, retired_attributes->sparse_construction.result_shape.valid() || !retired_attributes->sparse_construction.triplet_element_counts.empty() || retired_attributes->sparse_construction.reserve_hint != 0U || + retired_attributes->sparse_construction.value_domain != semantic::SparseValueDomain::none || + retired_attributes->sparse_construction.duplicate_policy != + semantic::SparseDuplicatePolicy::none || retired_attributes->sparse_index.valid() || retired_attributes->sparse_index.source_storage != ArrayStorageFormat::none || retired_attributes->sparse_index.result_storage != ArrayStorageFormat::none || @@ -1048,8 +1070,8 @@ void verify_expression(const Expression& expression, const Program& program, const auto scalar = semantic::sparse_index_returns_scalar(sparse_index.kind); const bool valid = source != nullptr && input_shape != nullptr && result_shape != nullptr && - element_type(program, source->type_id) == ValueType::real && - element_numeric_type(program, source->type_id) == real_numeric_type && + semantic::valid_sparse_stored_value_type(element_type(program, source->type_id), + element_numeric_type(program, source->type_id)) && sparse_index.input_shape == source->shape_id && sparse_index.result_shape == expression.shape_id && sparse_index.source_storage == array_storage(program, source->type_id) && @@ -1102,11 +1124,12 @@ void verify_expression(const Expression& expression, const Program& program, const bool valid = source != nullptr && input_shape != nullptr && requested_shape != nullptr && result_shape != nullptr && value_type(program, source->type_id) == ValueType::list && - element_type(program, source->type_id) == ValueType::real && - element_numeric_type(program, source->type_id) == real_numeric_type && + semantic::valid_sparse_stored_value_type(element_type(program, source->type_id), + element_numeric_type(program, source->type_id)) && value_type(program, expression.type_id) == ValueType::list && - element_type(program, expression.type_id) == ValueType::real && - element_numeric_type(program, expression.type_id) == real_numeric_type && + element_type(program, expression.type_id) == element_type(program, source->type_id) && + element_numeric_type(program, expression.type_id) == + element_numeric_type(program, source->type_id) && sparse_reshape.dimension_form == expected_form && empty_dimensions <= 1U && sparse_reshape.inference == expected_inference && (expected_inference == semantic::SparseReshapeInference::none || @@ -1145,6 +1168,9 @@ void verify_expression(const Expression& expression, const Program& program, array_storage(program, expression.type_id) == ArrayStorageFormat::sparse_csc && sparse.result_shape == expression.shape_id && result_shape != nullptr && result_shape->extents.size() == 2U && + sparse.value_domain == sparse_value_domain(program, expression) && + semantic::valid_sparse_construction_value_contract( + sparse.kind, sparse.value_domain, sparse.duplicate_policy) && std::none_of(result_shape->extents.begin(), result_shape->extents.end(), [](const auto extent) { return extent == std::numeric_limits::max(); @@ -1179,7 +1205,14 @@ void verify_expression(const Expression& expression, const Program& program, const auto* argument_shape = argument == nullptr ? nullptr : shape(program, argument->shape_id); valid = valid && argument_shape != nullptr && result_shape != nullptr && - argument_shape->extents == result_shape->extents; + argument_shape->extents == result_shape->extents && + sparse.value_domain == sparse_value_domain(program, *argument); + } else if (triplets) { + const auto* values = expression.children.size() >= 4U + ? mir::expression(program, expression.children[3U]) + : nullptr; + valid = valid && values != nullptr && + sparse.value_domain == sparse_value_domain(program, *values); } if (sparse.kind != semantic::SparseConstructionKind::triplets_reserved) { valid = valid && sparse.reserve_hint == 0U; @@ -1189,7 +1222,9 @@ void verify_expression(const Expression& expression, const Program& program, "sparse-construction attributes have an invalid shape or triplet count"); } } else if (sparse.result_shape.valid() || !sparse.triplet_element_counts.empty() || - sparse.reserve_hint != 0U) { + sparse.reserve_hint != 0U || + sparse.value_domain != semantic::SparseValueDomain::none || + sparse.duplicate_policy != semantic::SparseDuplicatePolicy::none) { add_error(diagnostics, expression.location, stage, "inactive sparse-construction attributes retain MIR state"); } diff --git a/src/ir/semantic_facts.hpp b/src/ir/semantic_facts.hpp index 7fea758..0466f83 100644 --- a/src/ir/semantic_facts.hpp +++ b/src/ir/semantic_facts.hpp @@ -80,6 +80,8 @@ struct SparseConstructionPlan { std::vector result_shape; std::vector triplet_element_counts; std::size_t reserve_hint{0U}; + semantic::SparseValueDomain value_domain{semantic::SparseValueDomain::none}; + semantic::SparseDuplicatePolicy duplicate_policy{semantic::SparseDuplicatePolicy::none}; [[nodiscard]] bool valid() const noexcept { return kind != semantic::SparseConstructionKind::none; diff --git a/src/ir/semantic_table.cpp b/src/ir/semantic_table.cpp index 42bc770..b8229ca 100644 --- a/src/ir/semantic_table.cpp +++ b/src/ir/semantic_table.cpp @@ -297,7 +297,8 @@ semantic::ReductionOperation expected_reduction_operation(const Expression& expr std::optional sparse_argument_count(const ExpressionFacts& facts) noexcept { if (facts.inferred_type != ValueType::list) { - return facts.inferred_type == ValueType::integer || facts.inferred_type == ValueType::real + return facts.inferred_type == ValueType::boolean || facts.inferred_type == ValueType::integer || + facts.inferred_type == ValueType::real ? std::optional{1U} : std::nullopt; } @@ -312,6 +313,20 @@ std::optional sparse_argument_count(const ExpressionFacts& facts) n return count; } +semantic::SparseValueDomain sparse_value_domain(const ExpressionFacts& facts) noexcept { + const auto type = + facts.inferred_type == ValueType::list ? facts.element_type : facts.inferred_type; + const auto numeric_type = expression_numeric_type(facts); + if (type == ValueType::boolean && numeric_type == logical_numeric_type) { + return semantic::SparseValueDomain::logical; + } + if ((type == ValueType::integer || type == ValueType::real) && + numeric_type.complexity == NumericComplexity::real) { + return semantic::SparseValueDomain::finite_real; + } + return semantic::SparseValueDomain::none; +} + semantic::SparseConstructionKind expected_sparse_construction_kind( const Expression& expression, const SemanticTable& table) noexcept { if (expression.kind != ExpressionKind::call || expression.children.empty()) { @@ -427,8 +442,9 @@ void verify_expression(const Expression& expression, const SemanticTable& table, } else if (sparse_index.valid()) { const auto* source = table.expression(expression.children.front().id); const bool scalar = semantic::sparse_index_returns_scalar(sparse_index.kind); - const bool valid = source != nullptr && source->element_type == ValueType::real && - source->element_numeric_type == real_numeric_type && + const bool valid = source != nullptr && + semantic::valid_sparse_stored_value_type(source->element_type, + source->element_numeric_type) && sparse_index.input_shape == source->shape && sparse_index.source_storage == source->array_storage && sparse_index.result_storage == facts->array_storage && @@ -480,10 +496,10 @@ void verify_expression(const Expression& expression, const SemanticTable& table, : semantic::SparseReshapeInference::none; const bool valid = source != nullptr && source->inferred_type == ValueType::list && - source->element_type == ValueType::real && - source->element_numeric_type == real_numeric_type && - facts->inferred_type == ValueType::list && facts->element_type == ValueType::real && - facts->element_numeric_type == real_numeric_type && facts->column_major && + semantic::valid_sparse_stored_value_type(source->element_type, + source->element_numeric_type) && + facts->inferred_type == ValueType::list && facts->element_type == source->element_type && + facts->element_numeric_type == source->element_numeric_type && facts->column_major && sparse.dimension_form == expected_form && empty_dimensions <= 1U && sparse.inference == expected_inference && (expected_inference == semantic::SparseReshapeInference::none || @@ -519,6 +535,9 @@ void verify_expression(const Expression& expression, const SemanticTable& table, bool valid = facts->inferred_type == ValueType::list && facts->array_storage == ArrayStorageFormat::sparse_csc && sparse.result_shape == facts->shape && sparse.result_shape.size() == 2U && + sparse.value_domain == sparse_value_domain(*facts) && + semantic::valid_sparse_construction_value_contract( + sparse.kind, sparse.value_domain, sparse.duplicate_policy) && std::none_of(sparse.result_shape.begin(), sparse.result_shape.end(), [](const auto extent) { return extent == dynamic_extent; }); const bool triplets = sparse.kind == semantic::SparseConstructionKind::triplets_inferred || @@ -547,7 +566,13 @@ void verify_expression(const Expression& expression, const SemanticTable& table, const auto* argument = expression.children.size() == 2U ? table.expression(expression.children[1].id) : nullptr; - valid = valid && argument != nullptr && argument->shape == sparse.result_shape; + valid = valid && argument != nullptr && argument->shape == sparse.result_shape && + sparse.value_domain == sparse_value_domain(*argument); + } else if (triplets) { + const auto* values = expression.children.size() >= 4U + ? table.expression(expression.children[3].id) + : nullptr; + valid = valid && values != nullptr && sparse.value_domain == sparse_value_domain(*values); } if (sparse.kind != semantic::SparseConstructionKind::triplets_reserved) { valid = valid && sparse.reserve_hint == 0U; @@ -557,7 +582,9 @@ void verify_expression(const Expression& expression, const SemanticTable& table, "sparse-construction plan has an invalid arity, shape, or triplet count"); } } else if (!sparse.result_shape.empty() || !sparse.triplet_element_counts.empty() || - sparse.reserve_hint != 0U) { + sparse.reserve_hint != 0U || + sparse.value_domain != semantic::SparseValueDomain::none || + sparse.duplicate_policy != semantic::SparseDuplicatePolicy::none) { add_error(diagnostics, expression.location, stage, "inactive sparse-construction plan retains semantic state"); } diff --git a/src/ir/semantics.hpp b/src/ir/semantics.hpp index af7cec7..04b12f9 100644 --- a/src/ir/semantics.hpp +++ b/src/ir/semantics.hpp @@ -5,6 +5,7 @@ #include #include "compiler/array_storage.hpp" +#include "compiler/numeric_type.hpp" #include "mpf/transpiler.hpp" namespace mpf::detail::semantic { @@ -272,6 +273,42 @@ enum class SparseConstructionKind : std::uint8_t { triplets_reserved }; +// Sparse storage alone does not identify the Matlab value class: an empty CSC matrix can be +// either double or logical. Keep the value domain and duplicate-triplet rule explicit so no +// later stage attempts to infer either property from stored values. +enum class SparseValueDomain : std::uint8_t { none, finite_real, logical }; +enum class SparseDuplicatePolicy : std::uint8_t { none, sum, logical_any }; + +[[nodiscard]] constexpr bool valid_sparse_stored_value_type( + const ValueType type, const NumericType numeric_type) noexcept { + return (type == ValueType::real && numeric_type == real_numeric_type) || + (type == ValueType::boolean && numeric_type == logical_numeric_type); +} + +[[nodiscard]] constexpr bool valid_sparse_construction_value_contract( + const SparseConstructionKind kind, const SparseValueDomain value_domain, + const SparseDuplicatePolicy duplicate_policy) noexcept { + if (kind == SparseConstructionKind::none) { + return value_domain == SparseValueDomain::none && + duplicate_policy == SparseDuplicatePolicy::none; + } + if (value_domain != SparseValueDomain::finite_real && + value_domain != SparseValueDomain::logical) { + return false; + } + const bool triplets = kind == SparseConstructionKind::triplets_inferred || + kind == SparseConstructionKind::triplets_sized || + kind == SparseConstructionKind::triplets_reserved; + if (triplets) { + return duplicate_policy == (value_domain == SparseValueDomain::logical + ? SparseDuplicatePolicy::logical_any + : SparseDuplicatePolicy::sum); + } + return duplicate_policy == SparseDuplicatePolicy::none && + (kind != SparseConstructionKind::zero_matrix || + value_domain == SparseValueDomain::finite_real); +} + // Sparse indexing has distinct scalar and storage-preserving result contracts. The selected // source form remains explicit so neither target backend infers linearization or Cartesian // submatrix semantics from selector count. diff --git a/src/semantic/expression_analyzer.cpp b/src/semantic/expression_analyzer.cpp index 92772de..f4ba8e2 100644 --- a/src/semantic/expression_analyzer.cpp +++ b/src/semantic/expression_analyzer.cpp @@ -57,16 +57,32 @@ std::optional checked_element_count(const std::vector& std::optional matlab_sparse_argument_count(const hir::ExpressionFacts& facts) { if (facts.inferred_type == ValueType::list) return checked_element_count(facts.shape); const auto type = facts.inferred_type; - return type == ValueType::integer || type == ValueType::real ? std::optional{1U} - : std::nullopt; + return type == ValueType::boolean || type == ValueType::integer || type == ValueType::real + ? std::optional{1U} + : std::nullopt; } -bool matlab_sparse_real_value(const hir::ExpressionFacts& facts) noexcept { +std::optional matlab_sparse_value_domain( + const hir::ExpressionFacts& facts) noexcept { const auto type = facts.inferred_type == ValueType::list ? facts.element_type : facts.inferred_type; const auto numeric_type = expression_numeric_type(facts); - return (type == ValueType::integer || type == ValueType::real) && numeric_type.present() && - numeric_type.complexity == NumericComplexity::real; + if (!numeric_type.present() || numeric_type.complexity != NumericComplexity::real) { + return std::nullopt; + } + if (type == ValueType::boolean && numeric_type.value_class == NumericClass::logical) { + return semantic::SparseValueDomain::logical; + } + if ((type == ValueType::integer || type == ValueType::real) && + (numeric_type.value_class == NumericClass::signed_integer || + numeric_type.value_class == NumericClass::binary64)) { + return semantic::SparseValueDomain::finite_real; + } + return std::nullopt; +} + +bool matlab_sparse_value(const hir::ExpressionFacts& facts) noexcept { + return matlab_sparse_value_domain(facts).has_value(); } std::optional matlab_sparse_literal_index_max(const Expression& expression) { @@ -2084,15 +2100,18 @@ ValueType Analyzer::analyze_call(Expression& expression) { auto& construction = facts.sparse_construction; if (arity == 2U) { const auto& argument = semantic(semantics_, expression.children[1]); + const auto value_domain = matlab_sparse_value_domain(argument); if (argument.inferred_type != ValueType::list || argument.shape.size() != 2U || - !known_shape(argument.shape) || !matlab_sparse_real_value(argument) || + !known_shape(argument.shape) || !value_domain.has_value() || !array_storage_known(argument.array_storage)) { diagnose(expression.location.line, "MPF2054", - "sparse(A) requires a statically shaped real rank-2 dense or CSC array"); + "sparse(A) requires a statically shaped real or logical rank-2 dense or CSC " + "array"); return facts.inferred_type = ValueType::unknown; } construction.kind = Kind::dense_conversion; construction.result_shape = argument.shape; + construction.value_domain = *value_domain; } else if (arity == 3U) { const auto rows = nonnegative_size_constant(expression.children[1]); const auto columns = nonnegative_size_constant(expression.children[2]); @@ -2103,17 +2122,22 @@ ValueType Analyzer::analyze_call(Expression& expression) { } construction.kind = Kind::zero_matrix; construction.result_shape = {*rows, *columns}; + construction.value_domain = semantic::SparseValueDomain::finite_real; } else { std::optional sequence_count; + std::optional value_domain; construction.triplet_element_counts.reserve(3U); for (std::size_t index = 1U; index <= 3U; ++index) { const auto& argument = semantic(semantics_, expression.children[index]); const auto count = matlab_sparse_argument_count(argument); - if (!count.has_value() || !matlab_sparse_real_value(argument)) { + const auto argument_domain = matlab_sparse_value_domain(argument); + if (!count.has_value() || !argument_domain.has_value()) { diagnose(expression.location.line, "MPF2054", - "sparse triplets require statically sized real numeric scalars or arrays"); + "sparse triplets require statically sized real numeric or logical scalars " + "or arrays"); return facts.inferred_type = ValueType::unknown; } + if (index == 3U) value_domain = argument_domain; construction.triplet_element_counts.push_back(*count); if (argument.inferred_type == ValueType::list) { if (sequence_count.has_value() && *sequence_count != *count) { @@ -2167,11 +2191,16 @@ ValueType Analyzer::analyze_call(Expression& expression) { construction.reserve_hint = *reserve; } } + construction.value_domain = *value_domain; + construction.duplicate_policy = *value_domain == semantic::SparseValueDomain::logical + ? semantic::SparseDuplicatePolicy::logical_any + : semantic::SparseDuplicatePolicy::sum; } facts.inferred_type = ValueType::list; facts.numeric_type = no_numeric_type; - facts.element_type = ValueType::real; - facts.element_numeric_type = real_numeric_type; + const bool logical = construction.value_domain == semantic::SparseValueDomain::logical; + facts.element_type = logical ? ValueType::boolean : ValueType::real; + facts.element_numeric_type = logical ? logical_numeric_type : real_numeric_type; facts.shape = construction.result_shape; facts.array_storage = ArrayStorageFormat::sparse_csc; return facts.inferred_type; @@ -2183,16 +2212,17 @@ ValueType Analyzer::analyze_call(Expression& expression) { return facts.inferred_type = ValueType::unknown; } const auto& argument = semantic(semantics_, expression.children[1]); + const auto value_domain = matlab_sparse_value_domain(argument); if (argument.inferred_type != ValueType::list || argument.shape.size() != 2U || - !matlab_sparse_real_value(argument) || !array_storage_known(argument.array_storage)) { + !value_domain.has_value() || !array_storage_known(argument.array_storage)) { diagnose(expression.location.line, "MPF2054", - "full requires a real rank-2 dense or CSC array with known storage"); + "full requires a real or logical rank-2 dense or CSC array with known storage"); return facts.inferred_type = ValueType::unknown; } facts.inferred_type = ValueType::list; facts.numeric_type = no_numeric_type; - facts.element_type = ValueType::real; - facts.element_numeric_type = real_numeric_type; + facts.element_type = argument.element_type; + facts.element_numeric_type = argument.element_numeric_type; facts.shape = argument.shape; facts.array_storage = ArrayStorageFormat::dense; return facts.inferred_type; @@ -2541,11 +2571,11 @@ ValueType Analyzer::analyze_index(Expression& expression, const bool container_a if (!container_already_analyzed) analyze_expression(container); const auto& container_facts = semantic(semantics_, container); const bool sparse_source = container_facts.array_storage == ArrayStorageFormat::sparse_csc; - if (sparse_source && (program_.language != SourceLanguage::matlab || - !static_rank_two_shape(container_facts.shape) || - !matlab_sparse_real_value(container_facts))) { + if (sparse_source && + (program_.language != SourceLanguage::matlab || + !static_rank_two_shape(container_facts.shape) || !matlab_sparse_value(container_facts))) { diagnose(expression.location.line, "MPF2054", - "sparse indexing requires a statically shaped real rank-2 CSC array"); + "sparse indexing requires a statically shaped real or logical rank-2 CSC array"); return semantic(semantics_, expression).inferred_type = ValueType::unknown; } if (container_facts.inferred_type != ValueType::list && @@ -3335,11 +3365,10 @@ ValueType Analyzer::analyze_reshape(Expression& expression) { facts.shape = std::move(dimensions); return facts.inferred_type; } - if (!matlab || source_facts.element_type != ValueType::real || - source_facts.element_numeric_type != real_numeric_type || source_facts.shape.size() != 2U || + if (!matlab || !matlab_sparse_value(source_facts) || source_facts.shape.size() != 2U || !source_size.has_value()) { diagnose(expression.location.line, "MPF2054", - "sparse RESHAPE requires a statically shaped real Matlab CSC matrix"); + "sparse RESHAPE requires a statically shaped real or logical Matlab CSC matrix"); return facts.inferred_type = ValueType::unknown; } std::size_t folded_columns = 1U; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index abb4f92..5c10e59 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -306,7 +306,7 @@ if(TARGET mpfc) mpf_add_generated_runtime_failure_test( matlab-sparse-assignment-nonfinite tests/fixtures/matlab_sparse_assignment_nonfinite.m - "sparse assignment requires finite real values" matlab) + "sparse assignment requires finite real or logical values" matlab) mpf_add_generated_plan_rejection_test( matlab-sparse-reshape-plan tests/fixtures/matlab_sparse_reshape_plan.m "result plan does not match requested dimensions" matlab @@ -318,6 +318,17 @@ if(TARGET mpfc) "mpf_runtime::sparse_reshape(A, std::array{2, 3}, std::array{3, 2}, std::array{3, 2})" CPP_REPLACEMENT "mpf_runtime::sparse_reshape(A, std::array{2, 3}, std::array{3, 2}, std::array{2, 3})") + mpf_add_generated_plan_rejection_test( + matlab-logical-sparse-plan tests/fixtures/matlab_logical_sparse_plan.m + "sparse input disagrees with its static shape contract" matlab + JAVASCRIPT_SEARCH + "__mpf_sparse_from_dense([[true, false], [false, true]], [2, 2], 2)" + JAVASCRIPT_REPLACEMENT + "__mpf_sparse_from_dense([[true, false], [false, true]], [1, 4], 2)" + CPP_SEARCH + "mpf_runtime::sparse_logical_from_dense(std::vector>{std::vector{true, false}, std::vector{false, true}}, std::array{2, 2})" + CPP_REPLACEMENT + "mpf_runtime::sparse_logical_from_dense(std::vector>{std::vector{true, false}, std::vector{false, true}}, std::array{1, 4})") mpf_add_generated_plan_rejection_test( matlab-sparse-product-plan tests/fixtures/matlab_sparse_product_plan.m "sparse matrix product shape plans are inconsistent" matlab diff --git a/tests/cmake/verify_compiler_layers.cmake b/tests/cmake/verify_compiler_layers.cmake index ce3ef9c..db0121b 100644 --- a/tests/cmake/verify_compiler_layers.cmake +++ b/tests/cmake/verify_compiler_layers.cmake @@ -532,6 +532,9 @@ if(NOT index_extent_contract MATCHES "MatrixConditionPolicy" OR NOT index_extent_contract MATCHES "valid_sparse_elementwise_contract" OR NOT index_extent_contract MATCHES "SparseConstructionKind" OR NOT index_extent_contract MATCHES "triplets_reserved" OR + NOT index_extent_contract MATCHES "SparseValueDomain" OR + NOT index_extent_contract MATCHES "SparseDuplicatePolicy" OR + NOT index_extent_contract MATCHES "valid_sparse_stored_value_type" OR NOT index_extent_contract MATCHES "SparseIndexKind" OR NOT index_extent_contract MATCHES "valid_sparse_index_contract" OR NOT index_extent_contract MATCHES "SparseMutationKind" OR @@ -547,6 +550,8 @@ if(NOT index_extent_contract MATCHES "MatrixConditionPolicy" OR NOT hir_extent_contract MATCHES "sparse_elementwise" OR NOT hir_extent_contract MATCHES "SparseConstructionPlan" OR NOT hir_extent_contract MATCHES "sparse_construction" OR + NOT hir_extent_contract MATCHES "SparseValueDomain value_domain" OR + NOT hir_extent_contract MATCHES "SparseDuplicatePolicy duplicate_policy" OR NOT hir_extent_contract MATCHES "SparseIndexPlan" OR NOT hir_extent_contract MATCHES "sparse_index" OR NOT hir_extent_contract MATCHES "SparseMutationPlan" OR @@ -560,6 +565,8 @@ if(NOT index_extent_contract MATCHES "MatrixConditionPolicy" OR NOT mir_extent_contract MATCHES "sparse_elementwise" OR NOT mir_extent_contract MATCHES "SparseConstructionPlan" OR NOT mir_extent_contract MATCHES "sparse_construction" OR + NOT mir_extent_contract MATCHES "SparseValueDomain value_domain" OR + NOT mir_extent_contract MATCHES "SparseDuplicatePolicy duplicate_policy" OR NOT mir_extent_contract MATCHES "SparseIndexPlan" OR NOT mir_extent_contract MATCHES "sparse_index" OR NOT mir_extent_contract MATCHES "SparseMutationPlan" OR @@ -589,6 +596,10 @@ if(NOT condition_lir_builder_contract MATCHES "sparse_construction\.kind = attributes\.sparse_construction\.kind" OR NOT condition_lir_builder_contract MATCHES "sparse_construction\.triplet_element_counts" OR + NOT condition_lir_builder_contract MATCHES + "sparse_construction\.value_domain = attributes\.sparse_construction\.value_domain" OR + NOT condition_lir_builder_contract MATCHES + "sparse_construction\.duplicate_policy = attributes\.sparse_construction\.duplicate_policy" OR NOT condition_lir_builder_contract MATCHES "sparse_index\.kind = attributes\.sparse_index\.kind" OR NOT condition_lir_builder_contract MATCHES @@ -614,6 +625,8 @@ foreach(target_lir IN ITEMS src/backends/javascript/lir.hpp src/backends/cpp/lir "SparseElementwiseStoragePolicy storage_policy" OR NOT condition_target_lir_contract MATCHES "SparseConstructionPlan" OR NOT condition_target_lir_contract MATCHES "SparseConstructionKind kind" OR + NOT condition_target_lir_contract MATCHES "SparseValueDomain value_domain" OR + NOT condition_target_lir_contract MATCHES "SparseDuplicatePolicy duplicate_policy" OR NOT condition_target_lir_contract MATCHES "SparseIndexPlan" OR NOT condition_target_lir_contract MATCHES "SparseIndexKind kind" OR NOT condition_target_lir_contract MATCHES "SparseMutationPlan" OR @@ -721,6 +734,10 @@ foreach(sparse_matrix_runtime IN ITEMS NOT sparse_matrix_runtime_contract MATCHES "sparse_from_triplets" OR NOT sparse_matrix_runtime_contract MATCHES "triplet indices must be positive safe integers" OR NOT sparse_matrix_runtime_contract MATCHES "duplicate accumulation is not finite" OR + NOT sparse_matrix_runtime_contract MATCHES + "(sparse_value_logical|sparse_logical_any)" OR + NOT sparse_matrix_runtime_contract MATCHES + "(sparse_duplicate_logical_any|LogicalAny)" OR NOT sparse_matrix_runtime_contract MATCHES "sparse_transpose" OR NOT sparse_matrix_runtime_contract MATCHES "sparse_linear_element" OR NOT sparse_matrix_runtime_contract MATCHES "sparse_subscript_element" OR @@ -788,7 +805,8 @@ foreach(sparse_representation IN ITEMS endif() endforeach() foreach(renderer IN ITEMS src/backends/javascript/renderer.cpp src/backends/cpp/renderer.cpp) - mpf_assert_file_excludes("${renderer}" "SparseConstructionKind|sparse_construction" + mpf_assert_file_excludes("${renderer}" + "Sparse(ConstructionKind|ValueDomain|DuplicatePolicy)|sparse_construction" "target renderer recovered sparse-construction semantics from source plans") mpf_assert_file_excludes("${renderer}" "MatrixStoragePolicy|sparse_csc_(multiply|scale)" "target renderer recovered sparse-product semantics from source plans") @@ -801,6 +819,21 @@ foreach(renderer IN ITEMS src/backends/javascript/renderer.cpp src/backends/cpp/ "target renderer does not serialize the verified target-call shape ABI: ${renderer}") endif() endforeach() +file(READ "${SOURCE_DIR}/src/backends/javascript/lir.hpp" javascript_sparse_call_abi) +file(READ "${SOURCE_DIR}/src/backends/javascript/lir_representation.cpp" + javascript_sparse_call_planner) +if(NOT javascript_sparse_call_abi MATCHES "runtime_integer_arguments" OR + NOT javascript_sparse_call_planner MATCHES + "runtime_integer_arguments =" OR + NOT javascript_sparse_call_planner MATCHES "__mpf_sparse_from_dense") + message(FATAL_ERROR + "JavaScript target LIR does not own the logical sparse runtime integer ABI") +endif() +file(READ "${SOURCE_DIR}/src/backends/cpp/lir_representation.cpp" cpp_sparse_call_planner) +if(NOT cpp_sparse_call_planner MATCHES "sparse_logical_from_dense" OR + NOT cpp_sparse_call_planner MATCHES "sparse_logical_any") + message(FATAL_ERROR "cpp target LIR does not own logical sparse helper selection") +endif() mpf_assert_file_excludes("src/backends/javascript/runtime.cpp" "function __mpf_matlab_lu_" "generic JavaScript runtime regained matrix factorization ownership") diff --git a/tests/differential/corpus.cmake b/tests/differential/corpus.cmake index 9f73e15..1039dc9 100644 --- a/tests/differential/corpus.cmake +++ b/tests/differential/corpus.cmake @@ -57,6 +57,8 @@ mpf_add_differential_case( mpf_add_differential_case( matlab-sparse-construction-transpose matlab examples/matlab/sparse_construction_transpose.m "0 0 2 2 2 2 2 1 1 4 5 5 5") +mpf_add_differential_case( + matlab-logical-sparse matlab examples/matlab/logical_sparse.m "2 2 2 2 2 2 2") mpf_add_differential_case( matlab-sparse-indexing matlab examples/matlab/sparse_indexing.m "3 4 5 3 1 2 1 4 3 2 5 1 4 3 2 5 5 5 1 4 2 5 5 1 5 5 1 0 0 2 1 5 2 1 2 1 1 2 2 1 1 1") diff --git a/tests/fixtures/matlab_logical_sparse_plan.m b/tests/fixtures/matlab_logical_sparse_plan.m new file mode 100644 index 0000000..d288302 --- /dev/null +++ b/tests/fixtures/matlab_logical_sparse_plan.m @@ -0,0 +1,2 @@ +A = sparse([true false; false true]); +disp(nnz(A)) diff --git a/tests/fuzz/corpus/matlab/logical_sparse b/tests/fuzz/corpus/matlab/logical_sparse new file mode 100644 index 0000000..579c0b4 --- /dev/null +++ b/tests/fuzz/corpus/matlab/logical_sparse @@ -0,0 +1,10 @@ +dense = sparse([true false; false true]); +duplicates = sparse([1 1 2 2], [1 1 2 2], [false true false true], 2, 2); +transposed = dense.'; +selected = duplicates([2 1], [2 1]); +reshaped = reshape(selected, [1 4]); +duplicates(1, 2) = true; +duplicates(2, 2) = false; +dense_full = full(dense); +result_full = full(reshaped); +disp(nnz(dense), nnz(duplicates), nnz(transposed), nnz(selected), nnz(reshaped), nnz(dense_full), nnz(result_full)) diff --git a/tests/golden/lir/cpp-basic.lir b/tests/golden/lir/cpp-basic.lir index 54fdb71..b363354 100644 --- a/tests/golden/lir/cpp-basic.lir +++ b/tests/golden/lir/cpp-basic.lir @@ -1,4 +1,4 @@ -cpp-semantic-lir-v34 revision 1 nodes 5 runtime 0x0 +cpp-semantic-lir-v35 revision 1 nodes 5 runtime 0x0 dependencies temporaries source-segments diff --git a/tests/golden/lir/javascript-basic.lir b/tests/golden/lir/javascript-basic.lir index 88141df..81308cf 100644 --- a/tests/golden/lir/javascript-basic.lir +++ b/tests/golden/lir/javascript-basic.lir @@ -1,4 +1,4 @@ -javascript-semantic-lir-v34 revision 1 nodes 5 runtime 0x0 +javascript-semantic-lir-v35 revision 1 nodes 5 runtime 0x0 dependencies temporaries source-segments diff --git a/tests/integration/array_tests.cpp b/tests/integration/array_tests.cpp index c8ec5a7..bd3990d 100644 --- a/tests/integration/array_tests.cpp +++ b/tests/integration/array_tests.cpp @@ -373,6 +373,10 @@ TEST_CASE("Matlab sparse CSC square solves preserve storage and target isolation REQUIRE(javascript.code.find("function __mpf_sparse_row_lu_factor") != std::string::npos); REQUIRE(javascript.code.find("__mpf_matlab_mldivide_sparse_real_square") != std::string::npos); REQUIRE(javascript.code.find("__mpf_matlab_mrdivide_sparse_real_square") != std::string::npos); + REQUIRE(javascript.code.find("__mpf_sparse_from_dense(result, resultShape, 1)") != + std::string::npos); + REQUIRE(javascript.code.find("__mpf_sparse_from_dense(result, resultShape)") == + std::string::npos); REQUIRE(javascript.code.find("std::vector") == std::string::npos); REQUIRE(cpp.code.find("struct sparse_matrix") != std::string::npos); REQUIRE(cpp.code.find("template void validate_sparse_csc(") != std::string::npos); @@ -745,7 +749,7 @@ TEST_CASE("Matlab sparse constructors and transpose preserve canonical CSC plans REQUIRE(javascript.success()); REQUIRE(cpp.success()); REQUIRE(javascript.code.find("function __mpf_sparse_from_triplets") != std::string::npos); - REQUIRE(javascript.code.find("__mpf_sparse(3, 4)") != std::string::npos); + REQUIRE(javascript.code.find("__mpf_sparse(1, 0, 3, 4)") != std::string::npos); REQUIRE(javascript.code.find("__mpf_sparse_transpose(cancelled)") != std::string::npos); REQUIRE(javascript.code.find("std::stable_sort") == std::string::npos); REQUIRE(cpp.code.find("sparse_from_triplets") != std::string::npos); @@ -778,10 +782,10 @@ TEST_CASE("Matlab zero-extent sparse constructors preserve their planned shape") const auto cpp = transpile_array(source, mpf::SourceLanguage::matlab, mpf::TargetLanguage::cpp); REQUIRE(javascript.success()); REQUIRE(cpp.success()); - REQUIRE(javascript.code.find("__mpf_sparse_from_dense(__mpf_empty_array([0, 0]), [0, 0])") != + REQUIRE(javascript.code.find("__mpf_sparse_from_dense(__mpf_empty_array([0, 0]), [0, 0], 1)") != std::string::npos); - REQUIRE(javascript.code.find("__mpf_sparse(0, 3)") != std::string::npos); - REQUIRE(javascript.code.find("__mpf_sparse(__mpf_empty_array([0, 0]), " + REQUIRE(javascript.code.find("__mpf_sparse(1, 0, 0, 3)") != std::string::npos); + REQUIRE(javascript.code.find("__mpf_sparse(1, 1, __mpf_empty_array([0, 0]), " "__mpf_empty_array([0, 0]), __mpf_empty_array([0, 0]))") != std::string::npos); REQUIRE(cpp.code.find("mpf_runtime::sparse_from_dense(") != std::string::npos); @@ -795,6 +799,41 @@ TEST_CASE("Matlab zero-extent sparse constructors preserve their planned shape") } } +TEST_CASE("Matlab logical sparse CSC values preserve class and lifecycle plans") { + const std::string source = + "dense = sparse([true false; false true]);\n" + "duplicates = sparse([1 1 2 2], [1 1 2 2], [false true false true], 2, 2);\n" + "transposed = dense.';\n" + "selected = duplicates([2 1], [2 1]);\n" + "reshaped = reshape(selected, [1 4]);\n" + "duplicates(1, 2) = true;\n" + "duplicates(2, 2) = false;\n" + "dense_full = full(dense);\n" + "result_full = full(reshaped);\n" + "disp(issparse(dense), issparse(duplicates), nnz(transposed), nnz(duplicates), " + "dense_full(1, 1), result_full(1, 1), result_full(1, 4))\n"; + const auto javascript = + transpile_array(source, mpf::SourceLanguage::matlab, mpf::TargetLanguage::javascript); + const auto cpp = transpile_array(source, mpf::SourceLanguage::matlab, mpf::TargetLanguage::cpp); + REQUIRE(javascript.success()); + REQUIRE(cpp.success()); + REQUIRE(javascript.code.find("__mpf_sparse_from_dense(") != std::string::npos); + REQUIRE(javascript.code.find(", [2, 2], 2)") != std::string::npos); + REQUIRE(javascript.code.find("__mpf_sparse(2, 2, ") != std::string::npos); + REQUIRE(javascript.code.find("valueDomain") != std::string::npos); + REQUIRE(cpp.code.find("mpf_runtime::sparse_logical_from_dense(") != std::string::npos); + REQUIRE(cpp.code.find("mpf_runtime::sparse_logical_any(") != std::string::npos); + REQUIRE(cpp.code.find("mpf_runtime::sparse_transpose(dense)") != std::string::npos); + REQUIRE(cpp.code.find("mpf_runtime::sparse_reshape(selected,") != std::string::npos); + REQUIRE(cpp.code.find("sparse_matrix") != std::string::npos); + for (const auto* result : {&javascript, &cpp}) { + for (std::size_t line = 1U; line <= 10U; ++line) { + REQUIRE(std::any_of(result->source_map.segments.begin(), result->source_map.segments.end(), + [line](const auto& segment) { return segment.original_line == line; })); + } + } +} + TEST_CASE("Matlab sparse CSC indexing preserves storage shape and target isolation") { const std::string source = "A = sparse([1 0 2; 0 3 0; 4 0 5]);\n" @@ -827,10 +866,10 @@ TEST_CASE("Matlab sparse CSC indexing preserves storage shape and target isolati REQUIRE(javascript.code.find("__mpf_sparse_submatrix_selection(A,") != std::string::npos); REQUIRE(javascript.code.find(", [null, 1])") != std::string::npos); REQUIRE(javascript.code.find("mpf_runtime::sparse_") == std::string::npos); - REQUIRE(cpp.code.find("double sparse_linear_element(") != std::string::npos); - REQUIRE(cpp.code.find("double sparse_subscript_element(") != std::string::npos); - REQUIRE(cpp.code.find("sparse_matrix sparse_linear_selection(") != std::string::npos); - REQUIRE(cpp.code.find("sparse_matrix sparse_submatrix_selection(") != std::string::npos); + REQUIRE(cpp.code.find("T sparse_linear_element(") != std::string::npos); + REQUIRE(cpp.code.find("T sparse_subscript_element(") != std::string::npos); + REQUIRE(cpp.code.find("sparse_matrix sparse_linear_selection(") != std::string::npos); + REQUIRE(cpp.code.find("sparse_matrix sparse_submatrix_selection(") != std::string::npos); REQUIRE(cpp.code.find("mpf_runtime::sparse_linear_element(A,") != std::string::npos); REQUIRE(cpp.code.find("mpf_runtime::sparse_submatrix_selection(A,") != std::string::npos); REQUIRE(cpp.code.find("std::nullopt, std::optional{1}") != std::string::npos); @@ -1016,7 +1055,7 @@ TEST_CASE("Matlab sparse reshape preserves CSC order and target isolation") { REQUIRE(javascript.code.find("__mpf_sparse_reshape(A, [2, 3], [1, 2, 3], [1, 6])") != std::string::npos); REQUIRE(javascript.code.find("mpf_runtime::sparse_reshape") == std::string::npos); - REQUIRE(cpp.code.find("sparse_matrix sparse_reshape(") != std::string::npos); + REQUIRE(cpp.code.find("sparse_matrix sparse_reshape(") != std::string::npos); REQUIRE(cpp.code.find("mpf_runtime::sparse_reshape(A, std::array{2, 3}, " "std::array{6, 1}, " "std::array{6, 1})") != std::string::npos); diff --git a/tests/performance/baseline.json b/tests/performance/baseline.json index 8d4a38a..2eb0b66 100644 --- a/tests/performance/baseline.json +++ b/tests/performance/baseline.json @@ -1,6 +1,6 @@ { "schemaVersion": 3, - "projectVersion": "0.6.8", + "projectVersion": "0.6.9", "maxLatencyNanoseconds": 1000000000, "minThroughputBytesPerSecond": 10000, "maxPeakArenaBytes": 67108864, @@ -27,6 +27,12 @@ "maxPeakArenaBytes": 8388608, "maxGeneratedBytes": 524288 }, + "matlab-logical-sparse": { + "maxLatencyNanoseconds": 250000000, + "minThroughputBytesPerSecond": 25000, + "maxPeakArenaBytes": 8388608, + "maxGeneratedBytes": 524288 + }, "matlab-sparse-index": { "maxLatencyNanoseconds": 250000000, "minThroughputBytesPerSecond": 25000, diff --git a/tests/performance/benchmark.cpp b/tests/performance/benchmark.cpp index 8e16df4..f71faf3 100644 --- a/tests/performance/benchmark.cpp +++ b/tests/performance/benchmark.cpp @@ -517,6 +517,52 @@ std::string matlab_sparse_elementwise_workload(const std::size_t width, const st return source; } +std::string matlab_logical_sparse_workload(const std::size_t width, const std::size_t rounds) { + const auto width_text = std::to_string(width); + const auto element_count = std::to_string(width * width); + std::string source; + source.reserve(width * width * 6U + width * 48U + rounds * 192U + 256U); + source = "dense = ["; + for (std::size_t row = 0U; row < width; ++row) { + if (row != 0U) source += "; "; + for (std::size_t column = 0U; column < width; ++column) { + if (column != 0U) source += ' '; + source += row == column || (5U * row + 3U * column) % 19U == 0U ? "true" : "false"; + } + } + source += "];\nmatrix = sparse(dense);\ntriplets = sparse(["; + for (std::size_t index = 0U; index < width; ++index) { + if (index != 0U) source += ' '; + source += std::to_string(index + 1U) + " " + std::to_string(index + 1U); + } + source += "], ["; + for (std::size_t index = 0U; index < width; ++index) { + if (index != 0U) source += ' '; + source += std::to_string(index + 1U) + " " + std::to_string(index + 1U); + } + source += "], ["; + for (std::size_t index = 0U; index < width; ++index) { + if (index != 0U) source += ' '; + source += index % 2U == 0U ? "false true" : "true false"; + } + source.append("], ").append(width_text).append(", ").append(width_text).append(");\n"); + for (std::size_t round = 0U; round < rounds; ++round) { + source += "transposed = matrix.';\n"; + source.append("selected = triplets([") + .append(width_text) + .append(" 1], [") + .append(width_text) + .append(" 1]);\n"); + source += "reshaped = reshape(selected, [1 4]);\n"; + source.append("triplets(1, ").append(std::to_string(round % width + 1U)).append(") = true;\n"); + source += "dense_result = full(reshaped);\n"; + } + source.append("disp(nnz(matrix) + nnz(triplets) + nnz(transposed) + nnz(dense_result) + ") + .append(element_count) + .append(")\n"); + return source; +} + std::string matlab_sparse_index_workload(const std::size_t width, const std::size_t rounds) { std::string source = "matrix = sparse(["; for (std::size_t row = 0U; row < width; ++row) { @@ -814,6 +860,8 @@ int main() { {"matlab-sparse-scale", matlab_sparse_scale_workload(24, 24), mpf::SourceLanguage::matlab}, {"matlab-sparse-elementwise", matlab_sparse_elementwise_workload(24, 24), mpf::SourceLanguage::matlab}, + {"matlab-logical-sparse", matlab_logical_sparse_workload(24, 24), + mpf::SourceLanguage::matlab}, {"matlab-sparse-index", matlab_sparse_index_workload(24, 24), mpf::SourceLanguage::matlab}, {"matlab-sparse-reshape", matlab_sparse_reshape_workload(24, 24), mpf::SourceLanguage::matlab}, diff --git a/tests/unit/multilevel_ir_tests.cpp b/tests/unit/multilevel_ir_tests.cpp index 27d2409..7742f54 100644 --- a/tests/unit/multilevel_ir_tests.cpp +++ b/tests/unit/multilevel_ir_tests.cpp @@ -1538,7 +1538,12 @@ TEST_CASE("target LIR verifiers independently reject corrupted sparse constructi expression.element_numeric_type = mpf::detail::real_numeric_type; expression.array_storage = mpf::detail::ArrayStorageFormat::sparse_csc; expression.shape = {3U, 4U}; - expression.sparse_construction = {Kind::triplets_reserved, {3U, 4U}, {2U, 2U, 2U}, 8U}; + expression.sparse_construction = {Kind::triplets_reserved, + {3U, 4U}, + {2U, 2U, 2U}, + 8U, + mpf::detail::semantic::SparseValueDomain::finite_real, + mpf::detail::semantic::SparseDuplicatePolicy::sum}; expression.children.resize(7U); auto& callee = expression.children[0]; callee.kind = mpf::detail::ExpressionKind::identifier; @@ -1587,6 +1592,99 @@ TEST_CASE("target LIR verifiers independently reject corrupted sparse constructi cpp.statements.front().expression.sparse_construction.result_shape[1] = 5U; mpf::detail::cpp::verify_lir_representation(cpp, diagnostics); REQUIRE(!diagnostics.empty()); + + const auto configure_logical_sparse_call = [&](auto& expression) { + configure_sparse_call(expression); + expression.element_type = mpf::detail::ValueType::boolean; + expression.element_numeric_type = mpf::detail::logical_numeric_type; + expression.sparse_construction.value_domain = mpf::detail::semantic::SparseValueDomain::logical; + expression.sparse_construction.duplicate_policy = + mpf::detail::semantic::SparseDuplicatePolicy::logical_any; + expression.children[3].element_type = mpf::detail::ValueType::boolean; + expression.children[3].element_numeric_type = mpf::detail::logical_numeric_type; + }; + + mpf::detail::javascript::lir::SemanticProgram logical_javascript; + logical_javascript.source_language = mpf::SourceLanguage::matlab; + logical_javascript.statements.resize(1U); + configure_logical_sparse_call(logical_javascript.statements.front().expression); + mpf::detail::javascript::plan_lir_representation(logical_javascript); + diagnostics.clear(); + mpf::detail::javascript::verify_lir_representation(logical_javascript, diagnostics); + REQUIRE(diagnostics.empty()); + REQUIRE(logical_javascript.statements.front().expression.plan.runtime_integer_arguments == + std::vector({2, 2})); + logical_javascript.statements.front().expression.plan.runtime_integer_arguments[1] = 1; + mpf::detail::javascript::verify_lir_representation(logical_javascript, diagnostics); + REQUIRE(!diagnostics.empty()); + + mpf::detail::cpp::lir::SemanticProgram logical_cpp; + logical_cpp.source_language = mpf::SourceLanguage::matlab; + logical_cpp.statements.resize(1U); + configure_logical_sparse_call(logical_cpp.statements.front().expression); + mpf::detail::cpp::plan_lir_representation(logical_cpp); + diagnostics.clear(); + mpf::detail::cpp::verify_lir_representation(logical_cpp, diagnostics); + REQUIRE(diagnostics.empty()); + REQUIRE(logical_cpp.statements.front().expression.plan.token == + "mpf_runtime::sparse_logical_any"); + logical_cpp.statements.front().expression.plan.token = "mpf_runtime::sparse"; + mpf::detail::cpp::verify_lir_representation(logical_cpp, diagnostics); + REQUIRE(!diagnostics.empty()); +} + +TEST_CASE("Matlab logical sparse construction keeps value and duplicate policies typed") { + auto lowered = lower_source(mpf::SourceLanguage::matlab, + "A = sparse([true false; false true]);\n" + "B = sparse([1 1 2], [1 1 2], [false true true], 2, 2);\n", + "sparse_logical_construction.m"); + auto analysis = mpf::detail::analyze_program(lowered.program, std::move(lowered.semantics)); + REQUIRE(analysis.empty()); + using Kind = mpf::detail::semantic::SparseConstructionKind; + using Domain = mpf::detail::semantic::SparseValueDomain; + using Duplicate = mpf::detail::semantic::SparseDuplicatePolicy; + const auto dense = std::find_if( + analysis.semantics.expressions.begin(), analysis.semantics.expressions.end(), + [](const auto& facts) { return facts.sparse_construction.kind == Kind::dense_conversion; }); + const auto triplets = std::find_if( + analysis.semantics.expressions.begin(), analysis.semantics.expressions.end(), + [](const auto& facts) { return facts.sparse_construction.kind == Kind::triplets_sized; }); + REQUIRE(dense != analysis.semantics.expressions.end()); + REQUIRE(triplets != analysis.semantics.expressions.end()); + REQUIRE(dense->element_type == mpf::detail::ValueType::boolean); + REQUIRE(dense->element_numeric_type == mpf::detail::logical_numeric_type); + REQUIRE(dense->sparse_construction.value_domain == Domain::logical); + REQUIRE(dense->sparse_construction.duplicate_policy == Duplicate::none); + REQUIRE(triplets->element_type == mpf::detail::ValueType::boolean); + REQUIRE(triplets->sparse_construction.value_domain == Domain::logical); + REQUIRE(triplets->sparse_construction.duplicate_policy == Duplicate::logical_any); + + auto corrupted_hir = analysis.semantics; + const auto corrupt = std::find_if( + corrupted_hir.expressions.begin(), corrupted_hir.expressions.end(), + [](const auto& facts) { return facts.sparse_construction.kind == Kind::triplets_sized; }); + REQUIRE(corrupt != corrupted_hir.expressions.end()); + corrupt->sparse_construction.duplicate_policy = Duplicate::sum; + REQUIRE(!mpf::detail::hir::verify_semantics(lowered.program, corrupted_hir, + "sparse-logical-policy-corruption") + .empty()); + + auto mir = mpf::detail::mir::lower_from_hir(std::move(lowered.program), + std::move(analysis.semantics), analysis.names); + REQUIRE(mir.diagnostics.empty()); + REQUIRE(mpf::detail::mir::verify(mir.program, "sparse-logical-construction").empty()); + REQUIRE(mpf::detail::dump_mir(mir.program).find("value-domain=2 duplicate-policy=2") != + std::string::npos); + const auto effects = mpf::detail::mir::analyze_alias_effects(mir.program); + const auto javascript = + mpf::detail::javascript::lower(mir.program, effects, mpf::TranspileOptions{}); + const auto cpp = mpf::detail::cpp::lower(mir.program, effects, mpf::TranspileOptions{}); + REQUIRE(javascript.diagnostics.empty()); + REQUIRE(cpp.diagnostics.empty()); + REQUIRE(javascript.artifact->debug_dump().find("value-domain 2 duplicate-policy 2") != + std::string::npos); + REQUIRE(cpp.artifact->debug_dump().find("value-domain 2 duplicate-policy 2") != + std::string::npos); } TEST_CASE("Matlab sparse reshape plans remain typed through every IR layer") { @@ -3245,7 +3343,7 @@ TEST_CASE("HIR and MIR dumps are deterministic and stage specific") { REQUIRE(!mpf::detail::hir::verify(invalid_hir_profile, "invalid-division-profile").empty()); const auto first_semantics = mpf::detail::dump_semantics(analysis.semantics); REQUIRE(first_semantics == mpf::detail::dump_semantics(analysis.semantics)); - REQUIRE(first_semantics.find("semantic-v20") != std::string::npos); + REQUIRE(first_semantics.find("semantic-v22") != std::string::npos); auto mir = mpf::detail::mir::lower_from_hir(std::move(lowered.program), std::move(analysis.semantics), analysis.names); @@ -3256,7 +3354,7 @@ TEST_CASE("HIR and MIR dumps are deterministic and stage specific") { const auto alias_effects = mpf::detail::mir::analyze_alias_effects(mir.program); const auto first_mir = mpf::detail::dump_mir(mir.program, alias_effects); REQUIRE(first_mir == mpf::detail::dump_mir(mir.program, alias_effects)); - REQUIRE(first_mir.find("mir-v26") != std::string::npos); + REQUIRE(first_mir.find("mir-v28") != std::string::npos); REQUIRE(first_mir.find("alias-effect-v3") != std::string::npos); REQUIRE(first_mir.find("memory-accesses=[") != std::string::npos); REQUIRE(first_mir.find("function @f") != std::string::npos); @@ -4725,9 +4823,9 @@ TEST_CASE("backends create isolated semantic pipelines and strongly typed LIR ar REQUIRE(!mpf::detail::javascript::lower(mir.program, stale_effects, options).diagnostics.empty()); const auto javascript_dump = javascript.artifact->debug_dump(); const auto cpp_dump = cpp.artifact->debug_dump(); - REQUIRE(javascript_dump.find("javascript-semantic-lir-v34") != std::string::npos); + REQUIRE(javascript_dump.find("javascript-semantic-lir-v35") != std::string::npos); REQUIRE(javascript_dump.find("expr %l") != std::string::npos); - REQUIRE(cpp_dump.find("cpp-semantic-lir-v34") != std::string::npos); + REQUIRE(cpp_dump.find("cpp-semantic-lir-v35") != std::string::npos); REQUIRE(cpp_dump.find("function-order") != std::string::npos); REQUIRE(javascript_dump == read_golden("lir/javascript-basic.lir")); REQUIRE(cpp_dump == read_golden("lir/cpp-basic.lir"));