Skip to content

feat(shader): add neutral IR and standalone diagnostics - #3054

Open
zhuxudong wants to merge 177 commits into
galacean:dev/2.0from
zhuxudong:refactor/shader-analyzer
Open

feat(shader): add neutral IR and standalone diagnostics#3054
zhuxudong wants to merge 177 commits into
galacean:dev/2.0from
zhuxudong:refactor/shader-analyzer

Conversation

@zhuxudong

@zhuxudong zhuxudong commented Jun 26, 2026

Copy link
Copy Markdown
Member
  • 设计依据:RFC RFC: Shader Static Analyzer #3017。核心参考 Naga 的 Frontend → IR → Validator/Info → Backend 分层,让解析、生成和诊断独立消费中立事实;本 PR 只建立未来后端边界,不实现 WGSL。

  • shader-parser 负责 ShaderLab/source-pass 解析并拥有 ShaderClueIRShaderCoreInfo 等中立结构;shader-compiler 只消费这些事实并通过 ShaderBackend 生成 GLES;shader-analyzer 作为同级消费者产出结构化 diagnostics,并提供浏览器 API 与 headless CLI。

  • parser 仅暴露 @galacean/engine-shader-parser/internal 运行时入口和 /internal/analyzer 分析入口,根包拒绝解析;已删除 _VERBOSE、jscc 双构建和 verbose 产物。冷构建通过显式 workspace runtime source 解析完成,不向 npm 包发布源码条件。

  • source parser 统一拥有 source error、entry binding、原始 range/source mapping 与 include canonical path;compiler 消费 typed parse envelope,结构性错误会在 precompile 序列化前失败,不再发布被静默丢弃 RenderState 的部分产物。

  • VariableDeclaratorInfo 是 const、initializer 与 array shape 的唯一事实;ShaderValidator 统一产生 NonConstInitializer 等诊断,局部、全局、数组和逗号 declarator 使用同一规则。

  • 宏分支分析采用三态契约:可证明安全时不报,确定错误时报 error 并保留 witness,无法证明时只报 warning;analyzer 不枚举全部宏组合,也不改变或阻断 runtime codegen。复杂或未知关系不被伪装成确定错误。

  • include 支持 canonical URL、嵌套相对路径、循环检测、双向 root 顺序回归与精确文件定位。默认运行时产物不包含 analyzer proof solver、authoring diagnostic 文案、分析 Lexer 或报告方法。

  • 基线固定为 dev/2.0@bd34daa45612af8b402cd3be916ff181f21ae742。本地完整 Chromium Vitest 为 141/141 个测试文件、445/445 个 suite、2149/2149 个用例;14 个包类型构建通过,22/22 个内置 Shader 在 bootstrap 与最终模块构建中均成功预编译。本次 head 的 GitHub CI 10/10 通过,覆盖三平台 build、四组 e2e、lint 与 codecov。

  • 当前 shipping Shader source 相对上一 PR head 无新增改动。对 dev/2.0 执行 22 个内置 Shader × 25 组宏配置、1300 个 stage 对比:49 个差异全部来自已知的非法 Fog/SSAO/Particle 重叠宏 fallback,unexpected mismatch 为 0;所有受支持配置与基线一致。真实 WebGL 预编译 A/B 为 57/57。

  • 相同 consumer entry 下,当前运行时 bundle 为 345601 B / 63149 B gzip;dev/2.0 基线为 383670 B / 65870 B gzip,分别减少 9.92% / 4.13%。产物与 sourcemap 门禁确认 runtime 未包含 BranchAnalysisAnalyzerLexerAnalyzerSemanticDiagnosticsPassParser 或 analyzer-only diagnostic 文案。

  • 同机交替 A/B 基准每轮包含 200 次 warmup、15 个交替 batch、每 batch 200 次。完整 pipeline 三轮结果为 +1.266% / +0.0368 ms、+2.527% / +0.0727 ms、-0.502% / -0.0146 ms;没有任何一轮同时超过 +3% 和 +0.05 ms 的回归门槛。

  • parser、compiler、analyzer 的 npm dry-run、resolver 与 export-target 门禁通过;parser 包为 80 个条目且不发布 src。Standalone CLI 已验证 clean=0、warning=0、error=1;8 份 parser/compiler/analyzer sourcemap 均包含完整 sourcesContent 且不泄漏绝对路径。

  • 已知边界:branch signature 不是 proof-complete 的 ESSL 宏求解器;不能证明的复杂宏关系保持 warning,最终 GLES driver 仍是运行时可接受性的事实来源。

zhuxudong added 30 commits June 2, 2026 11:07
- move createPosition/createRange + their pools to ShaderCompilerUtils
- move pass-text error context to ShaderCompilerUtils.processingPassText
- add ICodeGenVisitor interface so AST no longer imports concrete CodeGenVisitor
- parser/lexer/codegen now depend on ShaderCompilerUtils, not ShaderCompiler entry

prep for extracting shared shader-parser package (c3); no behavior change, 197 tests green
- copy ClearableObjectPool/IPoolElement into local common/ObjectPool
- add local no-op Logger (engine-core Logger is also disabled by default)
- copy render-state enums into common/enums/RenderStateEnums (values mirror engine-core)
- parser/lexer/lalr/sourceParser now engine-core-free; engine-math (Color) kept as foundation dep

deviates from RFC: Color kept as engine-math dep, render-state enums copied; 197 tests green
…mpiler

- move lexer/preprocessor/parser/lalr/AST/sourceParser + utils into @galacean/engine-shader-parser
- shader-compiler depends on it; cross-package imports go through the package barrel
- shader-parser ships one always-full build (jscc _VERBOSE=true), external to shader-compiler
- shader-parser drops stripInternal so compiler/analyzer can use internal parser APIs

pure relocation; 197 shader-compiler tests green
…gnostics

- new @galacean/engine-shader-analyzer drives the parse + collects diagnostics, skips codegen
- restores diagnostics the runtime compiler discards (parity verified vs verbose compiler)
- harvest approach: checks stay in shader-parser (single source), no visitor duplication
- Phase 1 returns GSError verbatim; structured API + new checks are Phase 2

harvest deviates from RFC's DiagnosticVisitor plan; 199 tests green
- analyzer now runs codegen too, capturing codegen-level diagnostics (struct/MRT/gl_FragData)
- ungate codegen error collection so the single release build always collects them
- remove ShaderCompiler._logErrors + calls: the compiler compiles, never reports
- delete the verbose build variant (/verbose export, rollup push, stub dir)
- shader-compiler drops stripInternal + exports GLES visitors so analyzer can drive codegen

completes Phase 1: diagnostics live in the analyzer; 200 tests green
- drop unused ObjectPool.garbageCollection (pools reuse via clear(), never GC)
- remove dead verboseMode branches from root rollup (no verbose build remains)
- collapse duplicate glslValidate calls left by the verbose→release test switch
- drop an obsolete warning-spy guard (the warning no longer exists; macro asserts cover it)
- tighten comments: drop task-context and a claim of a non-existent sync test
- remove unused abstract ObjectPool base class (only ClearableObjectPool
  extends it; inline the two fields)
- replace indirect ReturnType<typeof ShaderSourceParser.parse> with IShaderSource
- Diagnostic interface (severity, code, range, message, source, relatedSource)
- DiagnosticCode registry: C0 (parser/codegen), A1 (ShaderLab), B1/B2 (RenderState)
- gseErrorToDiagnostic converts GSError to structured Diagnostic
- ShaderAnalyzer.analyze() returns AnalysisResult.diagnostics: Diagnostic[]
- heuristic code mapping from GSErrorName + message content
- tests verify structured output for all 3 diagnostic sources
- reportWarning routed to Logger.warn, a noop since Phase 1 decoupled Logger
- the "declared before used" warning was silently dropped as a result
- now push CompilationWarn to errors[] (gated by _VERBOSE, like reportError)
- analyzer surfaces it as a C0-07 warning diagnostic; drop unused Logger import
- a failed function lookup is signature-keyed, conflating unknown names and wrong-arg calls
- both surfaced as one opaque "No overload function type found" message
- re-probe by name alone (+ builtin registry) to split the two cases
- unknown names now report a distinct "Undefined function" (C0-09); wrong-args keeps C0-06
- shader-parser always builds _VERBOSE=true, so its 88 #if _VERBOSE blocks were dead scaffolding
- the guarded code (diagnostics, line/column tracking) already shipped in every build
- strip all markers + drop the 2 dead #else console.error fallbacks
- dist and behavior identical to before; 202 shader tests stay green
- rollup _VERBOSE jscc is now a no-op (zero #if _VERBOSE left repo-wide) — remove it + the import
- VisitorContext location: any -> BaseToken["location"]; IRenderState drops the pointless | any
- BaseLexer throwError msgs: any[] -> unknown[] (only ever join()'d)
- map the "referenced X not found" codegen error to a dedicated C0-22 instead of the C0-08 fallback
- SymbolTable.insert silently overwrote a same-scope duplicate via a now-noop Logger.warn
- insert() now returns whether it replaced an equal symbol; decl sites surface it as a C0-10 warning
- macro-branch siblings stay exempt (insert skips isInMacroBranch entries) — covered by a test
- applies to local (SingleDeclaration/InitDeclaratorList) and global (VariableDeclaration) vars
- add PostfixExpression.semanticAnalyze: a `.field` on a known vector is validated as a swizzle
- catches out-of-range components (.z on vec2), mixed sets (.xr), bad chars, length > 4
- only fires when the base type is a concrete vecN — struct members and unresolved bases skip
- ParserUtils.swizzleError holds the rule; first C1 (GLSL type) layer check
- AssignmentExpression flags `a = b` when b's type cannot convert to a's
- ParserUtils.isAssignable models GLSL ES3 implicit conversions (int->float, ivecN->vecN)
- so valid coercions (float = int) are NOT flagged; only definite conflicts surface
- fires only when both operand types are concrete; compound RHS / structs are skipped
- JumpStatement.semanticAnalyze checks `return expr` against the function's declared return type
- reuses ParserUtils.isAssignable, so implicit conversions (return int from a float fn) pass
- skips void returns (C0-04 covers those) and unresolved/compound expressions
- ShaderTargetParser singleton: a failed parse (syntax error) left _traceBackStack dirty
- the next parse was then corrupted: a valid shader got a spurious diagnostic
- this hits the runtime compiler too (ShaderCompiler/ShaderAnalyzer share the singleton)
- clear _traceBackStack each parse; reset SymbolTableStack._macroLevel in clear() too
- regression test: a broken analyze() must not corrupt the following valid one
- registerRule(rule) runs user rules after the built-in checks on every analyze()
- rules get source + parsed structure + positionAt(); report() namespaces the code as <name>/<code>
- a throwing rule surfaces a <name>/rule-error warning instead of crashing analysis
- analyze() restructured so rules run even when structure parsing fails (built-in path unchanged)
- src/shader-playground.ts: a live editor + diagnostics panel driven by ShaderAnalyzer.analyze()
- no engine init (analyzer is standalone); the sample shows C0-09/10 and C1-01/02/03
- also demos registerRule via a "demo/no-discard" custom rule
- wire engine-shader-analyzer into examples deps + vite optimizeDeps exclude
- upgrade shader-parser's local Logger to a real controllable one (enable/disable, off by default)
- keeps zero engine-core dependency (local copy mirroring engine-core's API)
- route runtime console.* through it: error prints -> Logger.error, version banner -> Logger.info
- the compiler version banner no longer prints on every import (silent unless logging enabled)
- remove dead debug dumpers printStatePool / _printStack (uncalled) and their console
- bundler CLI keeps console (build-time terminal output); shader-analyzer had no bare console
- local common/Logger.ts existed to avoid an engine-core dep, but core never imports shader pkgs
- core injects shader-compiler (no import), so there was never a cycle to avoid
- depend on engine-core and use its Logger; redirect 4 parser + 1 compiler imports, drop the copy
- logging now unifies with the engine's Logger; 1428 tests pass, compiledShaders byte-identical
- the diagnostic package now logs each diagnostic via the engine Logger, off by default
- severity-mapped: error->error, warning->warn, info->info, hint->debug
- add @galacean/engine-core dep; analyze() logs after collecting all diagnostics
- enable Logger to see every syntax/semantic problem in the console while analyzing
…cal copy

- common/ObjectPool.ts was a local copy of core's pool (made to avoid the now-cycle-free core dep)
- core's ClearableObjectPool/IPoolElement are behavior-identical (same get/clear logic)
- redirect the 6 import sites to @galacean/engine-core; drop the local copy + its re-export
- 213 shader tests pass, compiledShaders byte-identical to dev/2.0
…al copy

- common/enums/RenderStateEnums.ts was a hand-synced local copy of core's 8 render-state enums
- drop it; ShaderSourceParser imports them from @galacean/engine-core (merged into its core import)
- no re-export consumer; design types render state by number, so no enum-identity issue at boundary
- compiledShaders byte-identical (render-state serialization unchanged); 213 tests pass
- rollup.config.js: drop the dead jscc plugin (no #if _VERBOSE left) + its stale verbose comments
- also drop a dangling src/enums/README.md reference in that file's header
- convert.ts: drop the "Phase 2 ... DiagnosticVisitor" promise (no DiagnosticVisitor was built)
- Preprocessor/Lexer: fix comments referencing the removed verbose build / wrong package
- drop ShaderInstructionEncoder's hand-synced local copy of the directive enum

- core now exports ShaderPreprocessorDirective publicly (values unchanged Text=0..Undef=10)

- compiledShaders stay byte-identical to dev/2.0; tsc clean across the 3 shader packages
…e registry

- gSErrorNameToCode returns DiagnosticCode.* refs, dropping 34 duplicated raw "C0-xx" literals

- return type is DiagnosticCodeValue so tsc rejects any code absent from the registry

- remove the never-passed defaultSeverity param (all five callers use the default)
- add SemanticWalker that walks the built AST and derives diagnostics from node type clues

- PostfixExpression still produces the type clue; swizzle check (C1-01) leaves its semanticAnalyze

- establishes parser-produces-clues / analyzer-judges pattern; first step of diagnostics decoupling

- compiledShaders byte-identical; full suite 1429 pass
- IntegerConstantExpressionOperator.compute is now optional; absence = unknown-operator clue

- C0-02 judgment leaves parser semanticAnalyze for the walker

- compiledShaders byte-identical; full suite 1429 pass
- remove SemanticWalker; swizzle (C1-01) and operator (C0-02) judgments go back to parser

- analyzer-side instanceof was a hack; judgment belongs internalized in parser clue computation

- correct model: error-as-clue in parser + analyzer generic collection

- compiledShaders byte-identical; 1429 tests pass
@zhuxudong zhuxudong added shader Shader related functions and removed documentation Improvements or additions to documentation labels Aug 10, 2026
@GuoLei1990
GuoLei1990 marked this pull request as draft August 18, 2026 09:46
- Reuse parsed shader passes across diagnostics and code generation.

- Preserve the runtime parser hot path and object pool.

- Report only proven macro conflicts.

- Reject invalid 32-bit preprocessor literals consistently.

- Add browser, package-consumer, isolation, and memory coverage.
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 24, 2026
@zhuxudong
zhuxudong marked this pull request as ready for review August 24, 2026 06:45
- Retain parser-owned preprocessor trees through analysis and code generation.

- Keep unsupported complex conditions silent instead of applying regex inference.

- Fix browser test roots and allow clean packed-package dependency resolution.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 a71791b72a1d55001ab6ff245b8c97a66508105f...6dc9d17aefea480b3d70ec7e74b8cbe06cb9ce82 的 2 个增量 commit、64 个文件完成审查,并沿 #if/#elif source → shared expression tree → ParsedShaderPass.blockingErrors → ShaderCompiler admission → ShaderInstructionEncoder → ShaderMacroProcessor/runtime variantShaderAnalyzer → retained parsed pass → ShaderCompiler 以及 package manifests → pack/install → ESM/CJS/.bin consumer 各追一层。GitHub lint、三平台 build、四组 E2E、coverage 与 package-consumer 所在 build 均成功;当前 PR 另被 GitHub 标记为 CONFLICTING / DIRTY。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 6dc9d17aefea480b3d70ec7e74b8cbe06cb9ce82。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮的两套 expanded-range/source-map 转换已收敛:ParsedShaderPass.sourceMap 继续拥有唯一映射事实,mapExpandedShaderRange 提供唯一纯转换,parser 规范化错误、analyzer 机械消费同一结果,compiler 不再另维护 formatter-side offset/position 算法(83c732b)。
  • 发布契约已由真实消费者闭环:CI 在 build 后打包并安装 math/design/core/parser/analyzer/compiler tarball,覆盖 parser root reject、ESM/CJS internal entries、shared chunks、安装生成的 .bin、include/stdin/diagnostic/exit code;没有恢复 Rollup chmod、raw-dist wrapper 或 legacy export(83c732b6dc9d17)。
  • 前轮 expression 问题中的两项子问题已关闭:analyzer-local validator 与 core-local evaluator/tokenizer 已由 design 包的共享 parser/evaluator 取代,parser-owned condition tree 会传到 encoder;evaluator failure 也不再被翻译成 silent false。尚未进入统一 admission 的确定性/展开期错误见问题 1。
  • include registry 的 prototype key、canonical include key/base/cache、source attribution、request-owned parser/semantic state、bounded GLES visitor pool,以及原有 branch/reachability/operator/declarator/source-structure gate 均继续保持关闭。
  • 前轮列出的 AndCondition/OrCondition/NotConditionGLESVisitorVisitorContextShaderPositionShaderRangeSemanticAnalyzer 与两个 @ts-ignore 已按多行 TSDoc或 @ts-expect-error 收口;本轮新增 surface 和残留项见问题 4。

问题

  1. [P1] packages/design/src/shader-compiler/PreprocessorExpression.ts:126-164 / packages/shader-parser/src/lexer/Lexer.ts:240-243,1021-1052 / packages/shader-parser/src/lexer/AnalyzerLexer.ts:422-436,496-527 / packages/shader-parser/src/parser/ParsedShaderPass.ts:213-242 / packages/shader-compiler/src/ShaderCompiler.ts:151-178 / packages/shader-compiler/src/ShaderInstructionEncoder.ts:164-175 / packages/core/src/shader/ShaderMacroProcessor.ts:336-360 / tests/src/shader-compiler/PreprocessorConditionConformance.test.ts:407-433 — 共享表达式 grammar 已落地,但确定性求值错误和 source-defined macro 展开错误仍不进入 compiler 的唯一 admission gate。 evaluateContextFreePreprocessorCondition 把“依赖宏”和“active divide/modulo by zero”都压成 undefined;Lexer 只把 parse failure 放进 expressionErrors,而 ParsedShaderPass.blockingErrors 只机械汇总这份数组。因此顶层 #if 1 / 0 会被 validated parser 接受、由 precompile 序列化成 IfExpr,随后每一次 runtime variant selection 都抛 Division by zero。同样,#define VALUE 4294967296 配合 #if VALUE + 0 时,AnalyzerLexer 虽保存了 replacement 的 valueError,但只在表达式能投影成 compact PreprocessorCondition 时调用 _findInvalidMacroValue;带算术的条件投影为 undefined,analyzer/precompile 均放行,错误被推迟到 runtime expansion。现有测试只覆盖 #if VALUE 的 compact 形态,并把 divide-by-zero / oversized expanded literal 直接从 encoder 送到 runtime,因此没有守住公开 precompile 契约。

    同一根因还使 deferred runtime path 重新维护了一套 lexical owner:共享 tokenizer 把 block comment 当空白,但 _evalDeferredCondition 用只接受 \s*defined 正则和字符串级 macro scanner。合法的 #define JOIN 1 &&#define FLAG 1#if JOIN defined/*comment*/(FLAG) 会先被正确判为“待展开”,runtime 却把 FLAG 展开成数字后再让共享 parser 拒绝 defined(1)

    请保留 design 包 parser/evaluator 为表达式 grammar 与求值结果的唯一 owner、ParsedShaderPass 为 parser/preprocessor facts 的唯一 owner、ShaderCompiler 为 backend admission 的唯一 owner:将 context-free 结果改为可区分 value / dependency / evaluation error 的 typed result,把确定性 active error 和已知 source-macro 展开错误映射进同一 source-mapped blockingErrors;deferred expansion 改为消费同一 token stream/规则,删除 AnalyzerLexer._findInvalidMacroValue 的 compact-only 校验和 core 的 defined regex/string lexical path。仅依赖外部 runtime macro 配置的错误可继续在 variant 边界显式失败,但不能让必败 artifact 通过 precompile。相应测试应从 _precompile 与 installed/runtime consumer 边界覆盖上述反例,不能再只锁定 direct encoder fallback。

  2. [P1] packages/shader-parser/src/lexer/Lexer.ts:1021-1056 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:36-51 / packages/shader-compiler/src/ShaderInstructionEncoder.ts:35-53,68-86,164-175 / tests/src/shader-analyzer/PreprocessorExpressionDiagnostics.test.ts:87-93 — analyzer 新接受的反斜杠续行条件在 codegen→encoder 边界被拆回物理行,合法 shader 无法编译或预编译。 Lexer 将 #if A && \ defined(B) 扫成一个逻辑表达式并缓存完整 parse tree,但会把反斜杠替换为空格而保留换行;默认 codegen 原样输出 token lexeme。Encoder 随后只读取到第一个物理行的 #if A &&,既无法命中以完整逻辑表达式为 key 的 parser map,又会独立重解析残缺条件并抛错。新增测试只断言 ShaderAnalyzer.analyze 无诊断,恰好绕过了发生协议断裂的 compiler/encoder 下游。

    请让 parser-owned logical directive/tree 贯穿到 instruction encoding,删除 encoder 对同一条件的 physical-line acceptance 规则;若仍需从生成文本扫描,至少与 Lexer 共享同一个 logical-line continuation scanner 和稳定 key。补 ShaderCompiler._parseShaderPass_precompile 以及两个 macro 配置的 runtime evaluation 回归,证明 analyzer、compiler admission 和 variant selection 消费同一逻辑条件,而不是为 analyzer-only 测试保留第二条语法协议。

  3. [P2] packages/shader-analyzer/src/ShaderAnalyzer.ts:48-68,89-108,145-166 / packages/shader-analyzer/src/index.ts:1-8 / tests/src/shader-analyzer/MacroBranchMatrix.test.ts:23-38 / tests/src/shader-compiler/StateIsolation.test.ts:89-103 / tests/package-consumer/PublishedShaderPackages.test.ts:141-180 — “first-party backend generation” handoff 仍没有生产消费者,只有测试在连接 analyzer 与 compiler 的两个 internal method。 仓内生产链仍是 Engine/ShaderCompiler 自己解析 runtime pass;ShaderAnalyzer.analyze 也仍只返回 diagnostics。当前新增了 AnalyzedShaderPassShaderAnalysisUnit、公开包导出的类型和 _analyzeWithParsedPasses,再由 synthetic matrix/memory/cache tests 调 _generateParsedShaderPass,但没有 first-party composition root 使用这条协议。相较改动前,编译器自己的 request-owned pool 已经解决热路径复用,这条跨包 handoff 只增加第三种 orchestration surface,并让测试成为它存在的唯一理由。

    请保留 ShaderAnalyzer.analyze 为 standalone diagnostics owner、保留 ShaderCompiler 内部的 runtime/validated parser + pool 为 codegen owner;删除 AnalyzedShaderPassShaderAnalysisUnit_analyzeWithParsedPasses 及其 package exports,将 parsed-pass generation 收回 compiler private implementation,并把 matrix/isolation/package-memory fixture 改为消费真实 analyzer 与 compiler 边界。真实 first-party composition root 落地时再在该层引入一次性 handoff,不要为未来消费者发布当前协议。

  4. [P2] packages/design/src/shader-compiler/PreprocessorExpression.ts:48-58 / packages/design/src/shader-compiler/ICondition.ts:53-56 / packages/shader-parser/src/ParserObjectPool.ts:30-75 / packages/shader-parser/src/parser/SemanticDiagnostics.ts:4-12 / packages/shader-parser/src/parser/ShaderTargetParser.ts:54-61,111-117 / packages/shader-compiler/rollup.config.js:48-51 / packages/shader-parser/src/parser/ShaderTargetParser.ts:170-171 / tests/src/shader-compiler/Precompile.test.ts:388 — 前轮注释/TSDoc 规范仍未一次性收口,本轮新增 surface 又重复了同类违规。 新的 exported interface/type 与 ParserObjectPool 五个 public method 仍使用单行 TSDoc,后者还缺参数/返回说明;ShaderTargetParser.create/setSource 同样没有完整多行契约。对照本轮已修正的 ShaderPosition.setShaderRange.setRuntimePassParser 同类 surface,这些写法不一致;BoolCondition 的多行说明仍没有句末句号。另有三处新增 // 以句号结尾。请删除问题 3 所述 dormant surface;其余保留的 class/interface/type/public method 一次性展开多行 TSDoc并补齐 @param name - description / @returns,去掉三处单行注释句号,不要为将删除的抽象补文档。

架构、熵增与测试治理

本轮有三处明确减熵:expression grammar/tree/evaluator 从 analyzer、parser、core 多套实现收敛到 design 包;expanded-range 映射从 analyzer + compiler-side formatter 收敛到 parser 的单一纯转换;发布验证从零变为 manifest → pack/install → ESM/CJS/.bin consumer 的真实链路。ParserObjectPool 只复用 compiler-owned 高水位载体,逐 pass reset,AST/diagnostic 不跨请求成为新权威状态;本轮逐类核对条件字段后未发现新的可观察状态泄漏。

但表达式链的 owner 数量尚未真正降到一份:parse tree 之外仍有 AnalyzerLexer 的 compact-only macro-value validator、Encoder 的 physical-line parser,以及 runtime 的 defined regex/string macro expander。于是语法 owner 虽合并,admission、logical-line 转换和 expansion 仍是平行协议,并已分别产生“必败 artifact 被预编译”“analyzer 接受而 compiler 拒绝”“共享 tokenizer 接受而 runtime regex 拒绝”的可观察分歧。按问题 1、2 保留 shared parser/tree + ParsedShaderPass gate、删除三条补偿路径后,owner、转换和校验数量才会净减少。

测试治理方向一正一负:packed consumer test 正确重写了已删除 raw-artifact fixture,没有恢复 chmod/wrapper/legacy fallback;malformed condition 也已从 silent-false 断言改为显式失败。反向上,divide-by-zero/invalid expanded literal 只测试 direct runtime,续行只测试 analyzer,synthetic parsed-pass reuse/memory tests又在维持一个无生产 owner 的协议。应按新的公开 precompile/runtime 契约重写这些 fixture,不能让测试继续驱动第二条 admission、第三条 orchestration 或 compatibility path。

shensi.zxd added 2 commits August 25, 2026 10:44
- share neutral parser IR and macro facts across analyzer and compiler
- align include resolution, source diagnostics and backend admission
- restore pooled hot paths and validate CLI, packages and runtime consumers
- preserve alpha.42 resource identity fixes while retaining shader source metadata

- block deterministic macro evaluation failures before runtime variants

- align new shader package versions with the alpha.42 workspace

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 6dc9d17aefea480b3d70ec7e74b8cbe06cb9ce82...b0e0c9488e37b2baf5aa1a7cab44736c8cab4aea 的 11 个增量 commit、165 个文件完成审查;其中 9 个是同步进来的 dev/2.0 已合入提交,PR 自身的响应范围为 a5d71da 的 104-file shader rewrite 与 b0e0c948 的 merge/admission resolution。已沿 Particle ShaderLibrary #define/#ifdef → AnalyzerLexer/BranchAnalysis → AST global-reference retention → GLESVisitor DCE → ShaderPrecompiler/.shaderc → ShaderMacroProcessor/WebGLShaderAnalyzer.analyze → opaque ParsedShaderPass → ShaderCompiler.generate → packed consumer 以及 package build → built-in shader precompile → E2E runtime 各追一层。lint、三平台 build、CodeCov 与 E2E 1/2/4 成功,E2E 3/4 失败。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 b0e0c9488e37b2baf5aa1a7cab44736c8cab4aea。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮的确定性预处理表达式与 source-defined macro admission 问题已关闭:design 包的 typed parser/evaluator 能区分 dependency 与 evaluation error,validated parser 将确定性错误统一纳入 source-mapped blocking gate,runtime expansion 也改为消费共享 token/parser 规则,没有恢复 defined regex、silent-false 或 compact-only validator。
  • 反斜杠逻辑续行协议已闭环:Lexer 与 instruction encoder 消费同一逻辑 directive,LF/CRLF、compiler/precompile 与 runtime macro variant 回归均覆盖,不再把 analyzer 接受的条件拆回物理行。
  • 原 analyzer→compiler dormant handoff 已改为显式公开契约:ShaderAnalyzer.analyze() 返回 opaque ParsedShaderPassShaderCompiler.generate() 机械消费同一 parser-owned payload;共享 package identity、README composition 和 pack/install consumer 均有验证,不再暴露可变 IR 或 internal method 对接。
  • expanded-range/source-map、发布包 ESM/CJS/.bin consumer、include registry/canonical cache、request-owned parser state、bounded pool,以及上轮 TSDoc/单行注释问题继续保持关闭。

问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:2033-2089 / packages/shader-parser/src/parser/AnalyzerSemanticDiagnostics.ts:28-40 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:99-102 / packages/shader-compiler/src/codeGen/GLESVisitor.ts:221-239,251-348 / packages/shader/src/ShaderLibrary/Particle/Module/VelocityOverLifetime.glsl:4-18 / packages/shader/src/ShaderLibrary/Particle/ParticleVert.glsl:115-125,178-191 — branch coverage 的 unknown 被当成“未引用”,离线 DCE 删除了运行时分支仍会使用的全局声明,内置粒子 shader 已无法通过 WebGL 编译。 这里 _VOL_LINEAR_MODULE_ENABLED 由 runtime RENDERER_VOL_* 宏派生,_VOL_MODULE_ENABLED 又由 linear/orbital 两类派生宏定义;因此声明位于 _VOL_MODULE_ENABLED && _VOL_LINEAR_MODULE_ENABLED,使用位于 _VOL_LINEAR_MODULE_ENABLED,逻辑上前者必然覆盖后者。本轮 symbolic branch proof 对这条嵌套蕴含保守返回 unknownbranchAvailability 正确地只把 uncovered 作为诊断;但 _lookupAndMarkGlobalReference 又用同一个 tri-state 驱动 backend liveness,在 coverage !== "covered" 时直接返回且不写入 referenceGlobalSymbolNames。下游 GLESVisitor 只从这份引用表生成 _referencedGlobalMacroASTs,于是把 int renderer_VOLSpace 删掉,却保留了 runtime 选中分支里的使用。

    这不是截图波动:目标 HEAD 的 E2E 3/4 在三次尝试中稳定失败 5 个 particle case;particleDream trace 的生成顶点 shader 在第 118 行使用 renderer_VOLSpace,声明列表中没有它,WebGL 明确报 'renderer_VOLSpace' : undeclared identifier,视觉差异为 73.5517708333%。上一 HEAD 的四个 E2E shard 均成功。

    请保留 AnalyzerLexer/BranchAnalysis 为 source-macro branch fact/coverage proof 的唯一 owner、GLESVisitor 为机械 DCE consumer,将“是否可发诊断”和“是否必须为 codegen 保留”拆开:uncovered 继续作为唯一可阻塞事实,unknown 则应保守保留所有 overlap 的 global candidate/AST,并在需要时维持 TypeAny,删除当前 coverage !== "covered" → return false 对引用保留的耦合。不要在 particle shader、core 或 runtime 增加 renderer_VOLSpace 特判、uniform fallback 或 compatibility branch。回归应使用这条真实派生宏链,从 ShaderPrecompiler.precompile 生成 instructions,再以 RENDERER_VOL_* runtime macro 展开并验证 WebGL declaration/use parity;现有 BranchAwareLookup.test.ts:374-391 只断言 analyzer 无诊断,无法守住 artifact 契约,失败的 E2E snapshot 也不应被更新来掩盖编译错误。

架构、熵增与测试治理

本轮总体有明显减熵:表达式 grammar/evaluator、确定性 admission、logical directive 与 runtime expansion 已收敛到共享契约;runtime compiler 与 offline validated precompiler 分层,公开 parsed-pass handoff 也由 opaque parser payload 和真实 package consumer 约束,没有恢复旧 wrapper、fallback 或镜像 IR。

剩余阻塞点是同一个 branch-coverage tri-state 被 AST 同时解释为“诊断证明”和“后端引用事实”:unknown 在诊断侧表示不能证明错误,在 DCE 侧却被翻译成确定未引用,形成“admission 成功但 artifact 必败”的平行状态。按问题 1 保留 AnalyzerLexer/BranchAnalysis 的唯一证明 owner、删除 AST 的非 covered 即丢弃规则,让 codegen 对未知事实机械保守,owner、状态与转换数量都无需增加。

测试侧,derived-macro fixture 已按新诊断契约重写,pack/install consumer 也正确验证了公开路径;但 analyzer-only 的 “no diagnostic” 与 precompile smoke 都没有执行 runtime-selected built-in variant,因而未发现声明/使用不闭合。应新增真实 precompile→runtime→WebGL 回归并保留当前红色 E2E,不能通过更新基线、生产 fallback 或第二套 macro implication evaluator让旧测试重新变绿。

- Retain guarded globals before analyzer coverage gates type inference.

- Add Particle and derived-macro runtime/precompile regressions.
@zhuxudong
zhuxudong requested a review from GuoLei1990 August 25, 2026 06:22

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 b0e0c9488e37b2baf5aa1a7cab44736c8cab4aea...b64bf174690f6183890691cfba0526302720df99 的 1 个增量 commit、3 个文件完成审查,并沿 Particle/source macro → AnalyzerLexer/BranchAnalysis → SymbolTableStack/AST global-reference retention → CodeGenVisitor/VisitorContext → GLESVisitor DCE → ShaderPrecompiler/ShaderMacroProcessor/WebGL 向上游和下游各追一层。上一轮失败的 E2E 3/4 已恢复,GitHub lint、三平台 build、四组 E2E、coverage 均成功;另在目标源码快照完成了全量 build,并对条件遮蔽反例执行了 analyzer→generate→runtime artifact 复现。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 b64bf174690f6183890691cfba0526302720df99。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮的 Particle 派生宏保活问题已关闭:AST._lookupAndMarkGlobalReference 现在会在 coverage/type gate 前记录候选全局,unknown 不再把真实全局误当成确定未引用;新增 Particle constant/curve 两个 precompile→runtime→WebGL 回归,原先失败的 E2E shard 也已成功。
  • 修复仍位于 parser-owned branch/liveness 链路,没有在 Particle shader、core 或 runtime 增加 uniform 特判、fallback、compatibility branch 或第二套 implication evaluator。
  • 共享表达式 parser/evaluator、确定性 admission、逻辑续行、opaque parsed-pass handoff、expanded-range/source-map、pack/install consumer、include canonical cache、request-owned parser state 与有界 visitor pool等此前已关闭项继续保持关闭。

问题

  1. [P1] packages/shader-parser/src/common/SymbolTableStack.ts:85-93 / packages/shader-parser/src/parser/AST.ts:1934-1974,2033-2090 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:99-102 / packages/shader-compiler/src/codeGen/VisitorContext.ts:209-218 / packages/shader-compiler/src/codeGen/GLESVisitor.ts:221-265 — 条件局部变量会截断外层全局的保活候选,合法 runtime 宏变体仍会生成“使用存在、全局声明被 DCE”的 shader。 本提交把全局记录提前到了 coverage gate 之前,但只能记录 lookupAll 返回的最近作用域候选;SymbolTableStack.lookupAll 一旦最近作用域找到同名 symbol 就无条件 break,因此看不到“该条件局部声明在当前宏变体中不存在时”应回退到的外层全局。

    已在目标 HEAD 用公开链路复现:声明全局 float branchValue;,在 #if defined(A) && VALUE * VALUE > 1 内声明同名局部变量,再在 #ifdef A 中读取 branchValue。Analyzer 对该 symbolic coverage 合理返回零诊断;但当 A 启用且 VALUE=0 时,预处理后的局部声明不存在,引用应解析到全局。当前 ShaderCompiler.generate 产物却完整删除了全局声明,同时保留 gl_FragColor = vec4(branchValue),runtime instruction 也没有全局声明,最终 WebGL 得到 undeclared identifier。新回归只有一个全局候选,没有覆盖条件局部遮蔽,所以无法发现这条变体。

    请保留 AnalyzerLexer/BranchAnalysis 为宏分支事实的唯一 owner、SymbolTableStack 为词法解析的唯一 owner,让同一解析结果同时携带“最近作用域语义候选”和“最近候选未证明覆盖 callsite 时的外层保活 fallback”;删除保活路径中 out.length > 0 → break 的无条件截断,以及 AST 通过 currentScopeSymbol/symbols.some 对截断结果二次猜测 global ownership 的逻辑。下游 CodeGenVisitor/VisitorContext/GLESVisitor 继续只机械消费 parser 记录的全局 identity/name。不要在 AST 或 backend 再做第二次 symbol-table 扫描,也不要改成无条件保留全部全局、增加 compatibility branch 或另一套 branch solver。回归应从 ShaderAnalyzer.analyze → ShaderCompiler.generate → ShaderMacroProcessor → WebGL 覆盖 A + VALUE=0/2 两个变体,并让 live compiler 路径消费同一 fixture,验证 declaration/use parity。

  2. [P2] packages/shader-parser/src/parser/AST.ts:2077-2078 — 本提交新增的两行 // 注释以句号结尾,不符合仓库单行注释不加句号的约定。 请移除末尾句号;无需改成额外文档抽象。

架构、熵增与测试治理

本轮对上一阻塞项是明确减熵:诊断 coverage 与 backend liveness 的语义已开始解耦,真实 Particle A/B 回归替代了 analyzer-only 断言,且没有把复杂度转移到 shader/runtime fallback。应继续保留 BranchAnalysis → SymbolTableStack → AST referenceGlobalSymbolNames → GLESVisitor 这条单向事实链。

剩余 P1 暴露的不是第三套宏规则,而是同一个 lookup 结果同时被解释为“最近词法声明”与“所有可能 runtime owner”:最近作用域的无条件 early-stop 对前者正确,对条件编译后的保活集合不完整,AST 再从这份截断集合推断 global 便形成漏失。按问题 1 让唯一的 SymbolTableStack resolution 显式产出保守 fallback、删除 AST 的 ownership 猜测后,不需要新增持久状态、镜像引用表或 backend 校验路径,owner 与转换数量均可保持不变。

测试侧,新增 Particle precompile/runtime/WebGL 用例正确关闭了真实故障,也不应更新 snapshot 来掩盖编译错误;但它只覆盖“全局候选自身受 guard 约束”,没有覆盖“内层条件声明遮蔽外层全局”的同根因矩阵。请按新的公开 artifact 契约补足该 fixture,不能为了既有测试在生产代码保留 nearest-scope fallback、全局全保留开关或第二套分支求值。

- Separate nearest-scope semantic candidates from runtime liveness fallbacks.

- Cover analyzer, live compiler, offline precompiler, and WebGL macro variants.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 b64bf174690f6183890691cfba0526302720df99...10a523eba454bca492b841733a2e2a357b8f3ea1 的 1 个增量 commit、5 个文件完成审查,并沿 AnalyzerLexer/BranchIdentity → SymbolTable/SymbolTableStack → AST global liveness 与 resolvedValueSymbols → ShaderCoreInfo/VisitorContext → CodeGenVisitor member projection → ShaderMacroProcessor/WebGL 向上游和下游各追一层。GitHub lint、三平台 build、四组 E2E 与 coverage 均成功,目标源码快照的全量 build/type build 也成功;但 live runtime compiler 的结构体分支遮蔽反例仍能稳定生成缺失 varying 且引用 v.uv 的非法 artifact。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 10a523eba454bca492b841733a2e2a357b8f3ea1。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮以 defined(A) && VALUE * VALUE > 1 条件局部变量遮蔽外层同名标量、导致 global declaration 被 DCE 的反例已关闭:新的 lookupAllWithRuntimeFallbacks 会保留外层 global name,新增 live/offline/analyzer-handoff 与 VALUE=0/2 回归覆盖了原始故障。
  • 上轮指出的 AST.ts 两处单行注释句尾句号已移除。
  • Particle 派生宏下 unknown 的保守全局保活,以及共享表达式 parser/evaluator、确定性 admission、逻辑续行、opaque parsed-pass handoff、expanded-range/source-map、pack/install consumer、include canonical cache、request-owned parser state 与有界 visitor pool等此前已关闭项继续保持关闭;本提交没有恢复 shader/runtime 特判、legacy fallback 或第二套 branch solver。

问题

  1. [P1] packages/shader-parser/src/common/SymbolTable.ts:123-140 / packages/shader-parser/src/common/SymbolTableStack.ts:107-147 / packages/shader-parser/src/parser/AST.ts:1935-1954 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:54-86 / packages/shader-compiler/src/codeGen/VisitorContext.ts:149-163 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:546-603 — 互斥分支中的局部声明仍留在 runtime lookup 的精确符号结果里,使同一引用的“全局保活名称”与“结构体 IO owner identity”分裂。 Runtime parser 没有 analyzer 的 branchSemantics,而 _getSymbols(..., includeMacro=true) 会先把同一词法作用域中、位于另一个 #if/#else arm 的局部 symbol 放进结果。新 lookup 随后虽然通过 isLexicalBranchVisibleFrom 发现它不覆盖 callsite,并把外层 global 加入 runtimeFallbacks,但仍将该互斥局部保留在主 out 中。VariableIdentifier 因而按 fallback name 保活外层 global,却把 _symbols 固定为局部 identity;下游 getStructVarRole 和 member projection 只消费精确 identity,于是两个事实给出相反 owner。

    已在目标 HEAD 用实际 analyzer/live/offline 链路复现:声明全局 Varyings v;,在 #ifdef LOCAL_SHADOW arm 声明局部 float v,在同组 #else arm 读取 v.uv。两个宏变体都合法,Analyzer 返回零诊断;analyzer handoff 与 offline precompiler 均正确生成 varying vec2 uv 并把成员访问投影为 uv。但 live runtime _parseShaderPass 在 GLSLES100 和 GLSLES300 都生成 v.uv,同时没有全局 v 或对应 varying;LOCAL_SHADOW 未定义时 artifact 因 undeclared identifier 必然无法通过 WebGL。现有新增回归的内外声明都是 float branchValue,只验证 name-based declaration/use parity,无法观察 exact symbol identity 与 struct-role projection,因此 checks 全绿没有覆盖这条协议断裂。

    请保留 AnalyzerLexer/BranchAnalysis 为 analyzer branch proof 的唯一 owner、BranchIdentity 为 runtime 可证明词法关系的唯一 owner、SymbolTable/SymbolTableStack 为词法解析的唯一 owner:在选择最近语义作用域前复用现有 getLexicalDeclarationCoexistence(或收敛为同一个词法 overlap predicate)过滤已证明 exclusive 的候选,让主 lookup 直接返回当前 runtime 变体可能拥有引用的精确 symbol;再让 global liveness name 与 resolvedValueSymbols() 从同一结果机械派生。具体应删除“互斥局部仍留在 out、外层 global 只作为 name fallback”的分裂路径,而不是在 AST 再扫一次 symbol table、在 backend 按名称猜 struct role、保留 compatibility branch,或引入第二套宏求解器。回归应使用外层 IO struct 与同一 #if/#else 组中的局部遮蔽,覆盖 analyzer handoff、live compiler、offline precompile、两个宏 arm 和 WebGL declaration/member parity;不要更新 snapshot 或增加生产 fallback 来迁就现有标量 fixture。

架构、熵增与测试治理

本轮对上一反例有明确减熵:AST 删除了 currentScopeSymbol/symbols.some 的 ownership 二次猜测,runtime fallback 被收回 parser-owned SymbolTableStack,scratch arrays 按 lookup 清空,没有新增持久状态、shader 特判或 compatibility path;标量端到端回归也替代了 analyzer-only 断言。

剩余 P1 表明这一收口尚未贯穿 identity consumer:同一次引用现在同时拥有 referenceGlobalSymbolNames 中的外层 global name 与 _symbols 中已被当前 arm 排除的局部 identity,下游 DCE/保活与 IO flattening 因而形成两份相互冲突的 owner 事实。按问题 1 在 SymbolTable/SymbolTableStack 删除 exclusive candidate、让 name 与 identity 机械派生自同一解析结果后,helper、状态和转换数量都无需再增加,也不会把复杂度转移到 AST、backend 或 runtime variant processor。

测试侧,新增 complex-condition 标量矩阵正确关闭了上一轮故障,既有 Particle/WebGL 和 package consumer 回归也无需恢复旧 fixture;但同型标量遮蔽天然掩盖 identity 漂移。应按当前公开 artifact 契约增加结构体 IO 遮蔽矩阵并保留 live/offline parity,不能为了已有测试在生产代码保留 name-only fallback、backend wrapper、镜像 role 状态或第二条转换/校验路径。

- Filter proven-exclusive candidates before nearest-scope selection.

- Cover struct IO shadowing across all three pipelines and GLES targets.

- Compile and link the generated programs in WebGL and WebGL2.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(P1)。已基于 10a523eba454bca492b841733a2e2a357b8f3ea1...8800df863abe22290a26801038f60c8c2a3cb7ef 的 1 个增量 commit、2 个文件完成审查,并沿 Lexer/AnalyzerLexer branch facts → BranchIdentity/BranchAnalysis → SymbolTable/SymbolTableStack → AST exact symbol 与 global liveness → VisitorContext/CodeGenVisitor member projection → ShaderMacroProcessor/WebGL 向上游和下游各追一层。目标源码快照的全量 build/type build 成功,GitHub lint、三平台 build、四组 E2E 与 coverage 均成功;新增的同组 #if/#else 结构体回归关闭了上一轮 P1,但本提交把跨独立条件组的同宏名相反 defined 也当成绝对互斥,遇到 #undef 后 live compiler 会静默读取错误的 owner。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 8800df863abe22290a26801038f60c8c2a3cb7ef。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮同一 #if/#else 组中局部声明残留在精确 symbol 结果、导致 global liveness 与结构体 IO owner identity 分裂的问题已关闭:SymbolTable 现在会在最近作用域选择前过滤同组互斥 arm;新增回归覆盖两个镜像 arm、analyzer handoff/live/offline、GLSLES100/300 与 WebGL/WebGL2 link。
  • 更早的复杂条件局部遮蔽外层全局保活、Particle 派生宏下 unknown 保守保活,以及两处单行注释句尾问题继续保持关闭;本提交没有恢复 AST/backend 的 name-based owner 猜测、shader 特判或 compatibility fallback。
  • 共享表达式 grammar/admission、逻辑续行、opaque parsed-pass handoff、source-map、pack/install consumer、include canonical cache、request-owned parser state 与有界 visitor pool等此前已关闭项未被本增量改动触碰。

问题

  1. [P1] packages/shader-parser/src/common/SymbolTable.ts:133-143 / packages/shader-parser/src/common/BranchIdentity.ts:54-87 / packages/shader-parser/src/lexer/Lexer.ts:136-233 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:54-86 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:627-705 — runtime candidate filter 把跨独立条件组的同名相反 guard 当成绝对互斥,#undef 后会选择错误的精确符号并静默改写结构体成员访问。 本提交在没有 analyzer branchSemantics 的 live 路径改用 getLexicalDeclarationCoexistence 过滤候选;该 helper 不仅证明同一 conditionalGroup 的不同 arm 互斥,还会仅凭 left.name === right.name && left.defined !== right.defined 将不同条件组判为 exclusive。然而 runtime Lexer 的 branch signature 没有携带宏 mutation/version,两个 guard 之间的 #define/#undef 可以让它们顺序同时成立,因此这里没有可删除局部 symbol 的事实依据。

    已在目标 HEAD 用公开 analyzer/live/offline 链路复现:声明外层 IO Varyings v;,在源码先 #define LOCAL_SHADOW,于 #ifdef LOCAL_SHADOW 中声明并初始化同名 LocalVaryings v,随后 #undef LOCAL_SHADOW,再在独立的 #ifndef LOCAL_SHADOW 中读取 v.uv。预处理后局部声明与读取确定共存,引用必须归属局部 v。Analyzer 返回零诊断,analyzer handoff 与 offline precompiler 在 GLSLES100/300 都正确生成 v.uv;上一 HEAD 10a523e 的 live compiler 也一致。目标 HEAD 的 live compiler 却把精确 identity 解析为外层 global,CodeGenVisitor 随即将访问投影成 varying uv:局部值 0.25 被静默替换为顶点 varying 0.5。这个错误 artifact 在 WebGL/WebGL2 仍能 compile/link,因此当前新增用例只覆盖同组 arm 并断言链接成功,无法观察该语义漂移。

    请保留 AnalyzerLexer/BranchAnalysis 为含 #define/#undef 状态的唯一 mutation-aware owner、Lexer/BranchIdentity 为 runtime 词法 group/arm identity 的唯一 owner、SymbolTable/SymbolTableStack 为解析 owner:删除“不同条件组仅凭同宏名且 defined 相反即可判定 exclusive”的无版本证明,让该关系在没有 mutation-aware fact 时保持 unknown;同一 conditionalGroup 的不同 arm 仍可机械判定互斥。不要在 backend 按名称补猜 role、复制一套 runtime macro-state solver、增加 compatibility branch 或镜像 owner 状态。回归应在当前三条 pipeline/两个 GLES target 矩阵中加入上述 define → local struct → undef → opposite guard use,至少断言 live 与 analyzer/offline 都保留 v.uv(或验证像素为局部值),而不是仅检查可链接性。

架构、熵增与测试治理

本轮对上一 P1 的直接路径有明确减熵:精确 symbol 与 global liveness 再次从同一次 parser lookup 机械派生,结构体 role consumer 无需新增 fallback;三条 pipeline、两个 target 和真实 WebGL link 的回归也替代了只看 analyzer 结果的窄断言。

但将 getLexicalDeclarationCoexistence 的跨组同名 heuristic 接入 _getSymbols 后,runtime 词法 identity 实际承担了它并不拥有的宏 mutation 事实,与 AnalyzerLexer/BranchAnalysis 形成两份结论:同一源码在 analyzer/offline 选择局部 owner,live compiler 选择 global owner。相较上一 HEAD,状态字段没有增加,但一个原本保守的未知关系被新增转换成确定 exclusivity,并把复杂度/错误转移到下游 member projection。按问题 1 删除这条无版本 cross-group proof、只消费同组 arm 或 mutation-aware owner 给出的事实后,owner、状态机和转换数量都无需增加。

测试侧,新增 fixture 正确关闭了同组互斥 arm 的旧故障,且不应删除;不过 compile/link 只能守住结构合法性,无法发现仍合法但读取错误 varying 的 artifact。应按新公开语义重写/扩展 fixture,验证精确 member projection 或渲染值;不能为了现有 link-only 测试在生产代码保留 name heuristic、backend wrapper、第二套宏状态或 compatibility fallback。

- Move proven semantic validation into the parser-owned offline pass.
  Keep runtime bundles free of diagnostic branches.
- Keep runtime branch ownership conservative across macro mutation.
  Align exact symbol identities with global liveness.
- Add analyzer/live/offline, package-consumer, benchmark, and macro regressions.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(3 个 P1,另有 1 个 P2)。已基于 8800df863abe22290a26801038f60c8c2a3cb7ef...176633caaeceaf9bd1e19d1a1f2f3821a96b8d3f 的 1 个增量 commit、12 个文件完成审查,并沿 branch identity / macro mutation → SymbolTableStack → AST exact candidates → ShaderCoreInfo / VisitorContext → CodeGenVisitor member projection → runtime variant,以及 parser semantic facts → ParsedShaderPass.blockingErrors → Analyzer opaque handle / offline precompile → ShaderCompiler.generate 各向上游和下游追了一层。上一轮跨独立条件组误判互斥的问题已关闭;目标源码快照的全量 build/type build 成功,GitHub lint、三平台 build、四组 E2E 与 coverage 均成功,但公开 API 复现仍分别产生“Analyzer 报错而 handoff 生成”、++/-- 必败源码被离线接受,以及宏变体下引用未声明 v 的 artifact。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 176633caaeceaf9bd1e19d1a1f2f3821a96b8d3f。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮“跨独立条件组仅凭同名宏且 defined 相反就判定 exclusive”的 P1 已关闭:getLexicalDeclarationCoexistence 现在只把同一 conditionalGroup 的不同 arm 判为互斥;新增 define → local struct → undef → opposite guard use 回归覆盖 analyzer handoff、live compiler、offline precompiler、GLSLES100/300、生成文本与 WebGL。
  • 更早的同组 #if/#else 精确 symbol identity、复杂条件局部遮蔽下的外层全局保活、Particle 派生宏 unknown 的保守保活,以及相关单行注释问题继续保持关闭;本提交没有恢复 backend 按名称猜 owner 或 runtime 宏求解器。
  • 共享表达式 grammar/admission、逻辑续行、opaque parsed-pass 公共契约、source-map、pack/install consumer、include canonical cache、request-owned parser state 与有界 visitor pool等此前已关闭项未被本增量恢复为 legacy/compat 路径。

问题

  1. [P1] packages/shader-parser/src/parser/ValidatedPassParser.ts:68-88 / packages/shader-analyzer/src/ShaderAnalyzer.ts:132-151 / packages/shader-compiler/src/ShaderCompilation.ts:44-57 / packages/shader-compiler/src/ShaderCompiler.ts:88-90 / tests/src/shader-compiler/ShaderNeutralIR.test.ts:306-320 — 新增 parser semantic gate 只接入 offline wrapper,Analyzer 返回的 opaque pass 仍绕过同一 admission。 parseValidatedShaderPass 会把 ParserSemanticValidation.collect() 的结果追加到 blockingErrors,但 Analyzer 走 parseShaderPass,并在 ShaderValidator.validate() 之前就用原始 parsed data 创建 handle;后者产生的 diagnostics 不会回写 handle。ShaderCompiler.generate() 又只机械检查 handle 中原有的 blockingErrors。因此同一份 void frag() { 1 = 2; ... } 在目标 HEAD 的公开链路得到三种结论:ShaderAnalyzer.analyze() 返回 InvalidAssignmentTargetnew ShaderCompiler().generate(result.passes[0], ...) 却成功生成含 1 = 2 的 fragment instruction,ShaderPrecompiler.precompile() 则拒绝。当前矩阵只断言 analyzer diagnostic 与 offline throw,恰好没有消费刚公开的 handoff。

    请保留 ParsedShaderPassData.blockingErrors 为唯一 backend-admission fact、ParserSemanticValidation 为这些共享规则的唯一 owner:在创建 opaque handle 前的共同 parsed-pass 路径收集并附着 parser 可证明的 semantic issues,让 Analyzer 只把同一错误机械映射为 diagnostics、compiler 只消费同一 blocking fact;随后删除 ShaderValidator 对这些共享规则的平行转发,以及 offline wrapper 中的第二次附加路径。不要要求调用方先检查 AnalysisResult.diagnostics 再决定能否 generate,也不要增加 wrapper、fallback 或第二套 gate。回归应让每个新增 semantic case 同时经过 Analyzer → pass → ShaderCompiler.generate 与 offline precompile,并断言二者给出同一拒绝结论。

  2. [P1] packages/shader-parser/src/parser/ParserSemanticValidation.ts:27-57,60-140 / packages/shader-analyzer/src/ShaderValidator.ts:358-383,548-564 / packages/shader-parser/src/parser/ValidatedPassParser.ts:68-88 / tests/src/shader-compiler/ShaderNeutralIR.test.ts:306-320 — 共享 l-value helper 已支持 increment target,但唯一 offline collector 没有分派 prefix/postfix ++/--,确定非法的 shader 仍被预编译。 collect() 只访问 assignment、condition、constructor 与 return 节点,没有为 UnaryExpression/PostfixExpression 产生 issue;Analyzer 的两个独立 visitor 分支却直接调用同一个 nonAssignableReason()。目标 HEAD 实测 const float locked = 1.0; ++locked;locked++; 都得到 InvalidAssignmentTarget diagnostic,但 offline precompile 成功,后者产物仍含 locked ++。这说明规则 body 虽已共享,规则覆盖/dispatch 仍有两份 owner;现有四项 offline matrix 只覆盖 1 = 2,未覆盖自增自减。

    请让 ParserSemanticValidation.collect 成为共享规则的唯一 dispatch owner,在其中覆盖 prefix/postfix increment(可抽成同一个 increment issue),再删除 Analyzer _checkUnaryOperand / _checkPostfix 中等价的 l-value 分支,仅保留 analyzer-only 的 operand、swizzle 与 index 检查。回归至少加入 prefix/postfix const,并从问题 1 所述 handoff/offline 两条公开边界验证;不能用 analyzer-only 校验或 runtime driver failure替代 admission。

  3. [P1] packages/shader-parser/src/parser/AST.ts:2028-2046 / packages/shader-compiler/src/codeGen/VisitorContext.ts:149-163 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:54-86 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:759-811 — 将条件局部与外层 fallback 合并成无配置关联的 symbol union 后,IO owner 与普通局部 owner 的分歧被翻译成普通成员访问,合法宏变体生成未声明的 v.uv 新代码把所有 runtimeFallbacks 直接追加进 VariableIdentifier._symbols;但这只保存了候选集合,没有保存“哪个宏配置选择哪个 owner”。getStructVarRole 遇到一个无 IO role 的局部候选便返回 undefinedCodeGenVisitor 又把 undefined 当成“普通 struct”,无条件输出 v.uv。我把新增 fixture 的外层普通 GlobalValue v 换成真实 vertex-output Varyings v 后,在 analyzer handoff、live compiler、offline precompiler及 GLSLES100/300 六个组合中都得到同一结果:LOCAL_SHADOW=false 时产物声明了扁平 varying uv、删除了全局 v,却仍读取 v.uvLOCAL_SHADOW=true 时该文本才正确归属局部。前者是确定的 undeclared identifier。当前 fixture 的外层与局部都是普通 struct,因此 v.uv 在两个配置里恰好都合法,WebGL link 也无法观察 owner 漂移。

    请保留 SymbolTableStack.lookupAllWithRuntimeFallbacks 为精确候选的唯一 owner、ShaderCoreInfo 的 exact-symbol role map 为唯一 IO-role owner,删除下游“候选 role 不一致 → undefined → 普通成员访问”的有损转换。候选若跨配置混合 IO/non-IO owner,应将配置关联贯穿到 instruction lowering;若本 PR 无法安全表达,最小正确边界是以明确的 branch-resolution ambiguity 阻断,而不是静默选中一种语法。不要在 backend 按名称猜 role、复制宏版本求解器、保留 v/uv 镜像声明或增加 compatibility branch。请把第二个回归改成真实 IO struct,覆盖初始宏 true/false、三条 pipeline 与两个 GLES target,并断言各配置生成正确的 uv/v.uv,或一致地显式拒绝。

  4. [P2] packages/shader-parser/src/parser/ParserSemanticValidation.ts:9,19 — 新增导出的 interface/class 使用了包含 @internal 的单行 TSDoc,与仓库 exported surface 的多行 TSDoc 规范不一致。 请按相邻 parser 公共/内部导出契约改为多行说明并将 @internal 独立成 tag;无需新增文档抽象。

架构、熵增与测试治理

本轮有两处明确减熵:删除跨独立条件组的无版本 exclusivity heuristic 后,runtime lexical identity 重新只声明自身能证明的事实;semantic rule body 从 Analyzer 大段实现收敛到 parser,构建产物也继续保持 runtime bundle 不含 analyzer-only validation。上一轮反例的新回归是有效替换,不应删除。

但 admission 的净变化是从同一 parsed-pass gate重新分裂为 offline collect → blockingErrorsAnalyzer validator → diagnostics-only handle 两条协议;同一 AST 因而出现 error / generate / reject 三态。规则实现虽减少,walker/dispatch 仍有两份并已漏掉 ++/--。符号侧新增候选没有新增持久宏状态,却把配置相关 ownership 压平成集合,再由下游把“无 role”和“role 冲突”都编码为 undefined,把复杂度转移成错误 artifact。按问题 1、2 让 common parsed pass 产生唯一 semantic blocking facts、按问题 3 删除有损 role fallback 后,owner、转换和校验数量才会净减少。

测试侧,第一组 source-defined macro fixture正确关闭了上一轮 P1;第二组 ordinary-struct fixture却无法观察 IO projection,offline semantic matrix也没有消费 opaque handoff且漏掉 increment。应按新的公开契约重写这些 fixture,直接验证 admission parity与配置相关 artifact;不得为了现有绿测在生产代码保留 analyzer-only gate、legacy fallback、wrapper、镜像状态或第二条转换/校验路径。

- Make parser-owned semantic failures the shared admission fact for analyzer and codegen pipelines.

- Preserve exact runtime owner candidates and reject unlowerable stage-IO ambiguity.

- Cover increment validation and direct or expression-macro ownership across GLES targets.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(2 个 P1)。已基于 176633caaeceaf9bd1e19d1a1f2f3821a96b8d3f...8b8ea345e426aed2c9f1402280302b013b1299b4 的 1 个增量 commit、13 个文件完成审查,并沿 parser semantic facts → ParsedShaderPass.blockingErrors → Analyzer opaque pass / offline precompile → ShaderCompiler.generate,以及 macro branch/callsite identity → VariableIdentifier candidates/runtimeFallbackReferences → ShaderCoreInfo owner facts → ShaderIOValidator/ShaderCompilation → CodeGenVisitor → runtime variant 各向上游和下游追了一层。目标源码快照的全量 build 成功,新增两组聚焦测试共 55/55 通过,GitHub 12 项 checks 全部成功;但公开 API 反例仍分别生成 undeclared v.uv 和把局部 v.uv 静默改写成 varying uv,且新增的 unknown-owner gate 会让 Analyzer/CLI 报 clean、同一 opaque pass 却必然无法生成。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 8b8ea345e426aed2c9f1402280302b013b1299b4。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮“parser semantic blocking fact 只接入 offline wrapper、Analyzer opaque pass 可绕过”的 P1 已关闭:parseShaderPassWith 现在在创建 handle 前统一收集并 source-map ParserSemanticValidation issues,Analyzer handoff 与 offline precompile 机械消费同一 blockingErrorsValidatedPassParserShaderValidator 中的重复附加/转发路径已删除。
  • 上轮 prefix/postfix ++/-- 未进入共享 collector 的 P1 已关闭:ParserSemanticValidation.collect 已统一分派两类 increment,Analyzer 的平行 l-value dispatch 已删除;新增 public matrix 同时覆盖 handoff/offline、GLSLES100/300、非法 const target 与合法可修改 target。
  • 上轮 ParserSemanticIssue / ParserSemanticValidation 单行 TSDoc 的 P2 已关闭,保留 surface 已改为多行契约并将 @internal 独立成 tag。
  • 更早的跨独立条件组 exclusivity、同组 exact identity、复杂条件 global fallback、Particle 派生宏保活、共享预处理表达式/逻辑续行、opaque handoff、source-map 与 pack/install consumer 等已关闭项继续保持关闭;本提交没有恢复 backend name guessing、runtime proof solver、legacy wrapper 或 compatibility fallback。

问题

  1. [P1] packages/shader-parser/src/ir/ShaderCoreInfo.ts:148-155,161-236 / packages/shader-parser/src/parser/AST.ts:1958-1969,2030-2065,2114-2120,2436-2467 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:54-86,199-215 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:926-994 — 新 owner gate 只枚举 outer-scope fallback 且只识别调用点外层的成员语法,仍会漏掉同级候选与 replacement 内部成员,三条公开 pipeline 都能生成错误 GLES。 collectStructMemberOwnerFacts 的输入只有 runtimeFallbackReferences;而 _registerRuntimeFallbackReference 在没有追加外层 fallback 时直接返回,所以同一词法 scope 的 #ifdef USE_IO { Varyings v; } #else { Ordinary v; } 两个 exact candidate 从不进入 gate。随后 getStructVarRole 面对 IO/ordinary 混合 owner 返回无 role,codegen 保留 v.uv,却删除 IO arm 的全局 v。目标 HEAD 实测 Analyzer 零诊断,analyzer handoff、live compiler、offline precompiler 在 GLSLES100/300 均成功;USE_IO=true 的 fragment artifact 引用 undeclared v.uv

    第二个同根因反例是把现有 fixture 的 #define OWNER v / OWNER.uv 改为合法的 #define OWNER_UV v.uv / OWNER_UV:replacement 内的 v 虽被 _collectIdentifierRefs 关联到 callsite branch,但原始 VariableIdentifier 位于 MacroDefine,不在 isReachableStructMemberReference 要求的 reachable function 下,因此也绕过 gate。六个 pipeline/target 组合都生成 #define OWNER_UV uv;当 callsite 的 conditional local v 存在时,正确值应为局部 v.uv = 0.25,产物却静默读取 outer varying uv = 0.5,即使 WebGL compile/link 成功也已改变语义。

    请保留 VariableIdentifier.resolvedValueSymbols() 与 macro callsite branch association 为 exact candidate 的唯一 owner、ShaderCoreInfo.structVariableRoles 为 IO role 的唯一 owner,删除“只有 outer fallback side table + caller-side parent shape 才参与 gate”的不完整事实边界:从每个 reachable member projection 机械派生 owner issue,覆盖同级 candidate union、outer fallback,以及 replacement AST 在各 callsite 下的投影。若某种配置关联仍无法安全降低,应进入同一个明确 blocking fact;不要在 backend 按名称猜 role、补镜像声明或增加 compatibility branch。回归应加入上述两个 fixture,覆盖 analyzer handoff/live/offline、两个 GLES target 与宏 true/false,并断言正确的 uv/v.uv,或三条路径一致地显式拒绝。

  2. [P1] packages/shader-parser/src/ir/ShaderCoreInfo.ts:88-95,154-155,161-215 / packages/shader-analyzer/src/ShaderIOValidator.ts:93-101 / packages/shader-compiler/src/ShaderCompilation.ts:96-99 / packages/shader-analyzer/src/cli.ts:58-73 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:762-800 — 同一个 owner issue 被持久化成 ambiguousunlowerable 两份真相,unknown 分支因此让 Analyzer/CLI 报 clean、compiler 却确定拒绝。 Collector 对 IO/ordinary 混合 owner 总是写 unlowerableLocations,仅在 coverage 为 uncovered 时另写 ambiguousLocations;Analyzer 只消费后者,compiler 只消费前者。新增测试明确锁定 analysis.diagnostics 为空,同时要求 handoff/live/offline 全部失败。公开 CLI 只依据 diagnostics 决定 exit code,所以该必败 pass 会返回 0;而 README 展示的 analyze → passes → generate 组合又没有任何结构化原因可供调用方展示。这不是保守 proof 本身的问题,而是同一确定的 backend-admission 结果在上下游被投影成 clean/fail 两种协议。

    请保留一份带 location(若需要再带 proof classification)的 canonical owner issue 作为唯一 backend-admission fact,删除 ambiguousStructMemberOwnerLocations / unlowerableStructMemberOwnerLocations 的平行 subset/superset 状态;Analyzer 应把每个会让 generate 必然失败的 issue 机械映射为明确的 backend-unlowerable error,compiler 消费同一列表。不要要求调用方先试 generate、解析 Logger 文本或复制第二套 gate。相应用例应改为同时断言结构化 diagnostic 与三条生成路径的一致拒绝,而不是让测试要求生产代码保留“analysis silent / codegen blocking”的双协议。

架构、熵增与测试治理

语义 admission 侧是明确减熵:共享规则的 collection、dispatch 与 blocking fact 已收敛到 parser common path,Analyzer 只映射同一错误,compiler 只 admission;prefix/postfix 与其他 semantic cases 的公开矩阵正确替换了原来的 analyzer-only/offline-only断言,runtime lean parser 也没有被迫携带 authoring validator。

宏 owner 侧则从 per-reference exact candidates 新增了 runtimeFallbackReferences side table,再派生 ambiguousunlowerable 两个持久列表;相较上一 HEAD,状态与转换净增三层,却仍漏掉同级 owner 和 replacement-body projection,并让 Analyzer 与 compiler 各消费一份不同结论。按问题 1 从全部 reachable projection 机械派生、按问题 2只保留一份 canonical issue 后,candidate、role、admission 各回到一个 owner,无需第三套宏求解器或 backend fallback。

测试侧,semantic matrix 应完整保留;新增 direct v.uvOWNER.uv 和 GLSL/WebGL 矩阵有效覆盖了已接入路径,但没有覆盖 same-scope union 与 #define OWNER_UV v.uv,且 unknown 用例正把新的 clean/fail 分裂固化成契约。应按公开 artifact/admission 契约补写或重写这些 fixture;不得为了现有绿测保留 partial side table、silent gate、name-based wrapper、镜像状态或第二条转换/校验路径。

- Derive one canonical owner issue from direct and macro-replacement projections.

- Keep unknown macro relations silent in Analyzer while blocking unsafe Codegen output.

- Cover same-scope and replacement-member regressions across all compiler paths.
GuoLei1990

This comment was marked as outdated.

@zhuxudong

Copy link
Copy Markdown
Member Author

P1

问题:已修复 owner gate 只覆盖 runtime fallback 的缺口。Parser 现在同时收集直接 member projection 和宏替换 AST 在调用点的解析快照,Core 统一收敛为 structMemberOwnerIssues,覆盖 same-scope union 及 #define OWNER_UV v.uv。(ShaderCoreInfo.ts:156MacroBranchRuntime.test.ts:997

预期:Analyzer 和 Codegen 共享同一事实,但保留不同策略:能证明冲突的 definite 在 Analyzer 与 Codegen 中一致失败;无法证明宏关系的 unknown 不作为 Analyzer 诊断,Codegen 因无法保证安全 lowering 而拒绝生成。

Analyzer 不持有 backend codegen 状态,也无法穷举任意宏表达式、运行时宏组合和 backend-specific lowering 失败,因此无法保证提前检测所有 Codegen failure。若你认为这个边界不可行,请给出一个同时满足“Analyzer 不依赖 backend、unknown 不误报、覆盖全部 Codegen failure”的具体方案或接口契约,我再按该方案分析可行性。

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(2 个 P1,另有 1 个 P2)。远端 HEAD 自上一份 review 后没有代码变化,仍为 2b1fb1a;本轮会话增量只有作者于 2026-08-27T05:06:09Z 对 unknown owner policy 的回复。已重新核对 8b8ea34...2b1fb1a 的 1 个 commit、6 个文件,并沿 macro definition / formal-actual binding / branch identity / exact symbols → reference snapshots / replacement projection → ShaderCoreInfo owner issue → Analyzer policy / compiler admission → CodeGenVisitor / runtime expansion 向上游和下游各追一层。作者对 unknown 分层的解释成立并已关闭;但函数式与嵌套 replacement 的漏检、以及跨独立宏定义的 path-level 合并仍在同一源码快照中。上一轮针对该不可变 HEAD 的全量 build 和公开 API 正反例探针证据继续有效,当前 GitHub 12 项 checks 也全部成功。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 2b1fb1a。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮“unknown 在 Analyzer 中 silent、在 Codegen 中 blocking 即两套 admission”的 P1 已按作者解释关闭。AnalysisResult 的公开契约只承诺结构化诊断与可复用 pass,ShaderAnalyzer 明确不生成 backend source,ShaderCompiler.generate 也显式允许返回 undefined;因此同一 structMemberOwnerIssues 事实由 Analyzer 只诊断 definite、由 emitter 对 unknown 保守拒绝,是职责不同的 policy projection,不要求 Analyzer 覆盖全部 backend failure。无需让 CLI 为 unknown 返回非零,也无需新增 backend diagnostic mirror。
  • 同一词法 scope 的 IO/ordinary exact candidate union,以及直接对象式 replacement #define OWNER_UV v.uv,已由本 HEAD 的 directMemberOwnerReferences 与 referenceResolutionSnapshots 纳入 canonical owner issue;对应三条 pipeline、两个 GLES target 回归继续保留。
  • ambiguousStructMemberOwnerLocations / unlowerableStructMemberOwnerLocations 两份持久 verdict 已收敛为一份带 certainty 的 structMemberOwnerIssues;本轮没有发现该 canonical fact 被重新分叉。
  • 更早的 parser semantic common gate、prefix/postfix increment collector、跨独立条件组 exclusivity、同组 exact identity、复杂条件 global fallback、Particle 派生宏保活、共享预处理表达式与逻辑续行、opaque handoff、source-map 和 pack/install consumer 等已关闭项继续保持关闭;本轮没有恢复 backend name guessing、runtime proof solver、legacy wrapper 或 compatibility fallback。

问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:2469-2509,2529-2559 / packages/shader-parser/src/ir/ShaderCoreInfo.ts:194-256 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:148-185,199-215 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:1056-1086 — replacement projection 仍没有建模函数式宏的 formal→actual 绑定,也不会递归嵌套 replacement,公开 pipeline 会生成引用 undeclared v 的 GLES。 _collectIdentifierRefs 在参数命中时跳过 identifier,并在遇到任意 VariableIdentifier 后直接 return;因此 GET_UV(x) 的 x.uv 不会投影到 GET_UV(v) 的实际 owner,child 为 MacroCallSymbol 的 OWNER.uv 也不会继续下钻。MacroCallFunction 只复制宏名节点已有 references,没有建立实参与形参的 exact-symbol 关系。作者回复所述 same-scope 与直接 #define OWNER_UV v.uv 覆盖不包含这两种形态。对同一目标 HEAD 已验证 #define GET_UV(x) x.uv + GET_UV(v),以及 #define OWNER v + #define OWNER_UV OWNER.uv:Analyzer 无诊断,analyzer handoff、live compiler、offline precompiler 在 GLSLES100/300 均成功;runtime instruction expansion 后 fragment 仍含 v.uv,而 IO 全局 v 已被 flatten/delete,只留下 uv,产物引用未声明变量。

    请保留 parser 的 macro definition / formal-actual binding / branch identity 与 VariableIdentifier.resolvedValueSymbols() 为 owner projection 的唯一事实,ShaderCoreInfo.structVariableRoles 为 IO role 的唯一事实;从每个实际 emitted member occurrence 机械派生函数实参与 nested replacement 的 owner resolution,删除“只收集 replacement 内非形参 BaseToken”的不完整边界。无法安全降低的形态应进入现有 canonical issue;不要在 backend 按名称猜 role、复制宏求解器、补镜像声明或 compatibility branch。回归应加入上述两个 fixture,覆盖 analyzer handoff、live/offline、GLSLES100/300 和 runtime expansion,并断言正确 uv / v.uv,或三条路径一致地显式拒绝。

  2. [P1] packages/shader-parser/src/parser/ShaderInfo.ts:23-29 / packages/shader-parser/src/ir/ShaderCoreInfo.ts:236-256,271-307 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:199-215 — replacementMemberOwnerPath 仍把互斥宏定义中的独立 replacement 合并成一份 role union,拒绝了 backend 已能逐定义正确降低的合法变体。 replacementGroups 的 key 只有同一个 call-site reference 与 replacement 内结构 path;两个 MacroDefine.valueExpression 只要形态相同就会被并组,即使它们来自同一 #if/#else 的不同定义。目标 HEAD 对 USE_IO 两个 arm 分别定义 OWNER_UV 为 io.uv 与 ordinary.uv 的 shader 报 AmbiguousMacroBranchResolution,并让三条生成路径、两个 target 全部拒绝;而绕过新增 gate 后,同一个 CodeGenVisitor 已分别把 true arm 降为 uv、false arm 保持 ordinary.uv,声明与引用均闭合。这里不存在同一 emitted occurrence 的 owner 冲突,冲突是 path-only union 人为制造的。

    请保留每个 MacroDefine.valueExpression + branch identity 作为 emitted replacement 的 owner,删除跨定义仅凭相同字符串 path 做 role union 的转换;只有真正流入同一 emitted occurrence 的 call-site-dependent resolutions 才合并。无需增加 backend resolver,现有 CodeGenVisitor 已逐定义转换 replacement。回归应覆盖 analyzer handoff、live/offline、两个 target 和 USE_IO true/false,并分别断言 uv / ordinary.uv;不能用扩大 ambiguity gate、path-level 镜像状态或旧拒绝测试来维持当前 false positive。

  3. [P2] packages/shader-parser/src/ir/ShaderCoreInfo.ts:55-61 — 新增 exported ShaderStructMemberOwnerIssue 仍把说明与 @internal 压在单行 TSDoc。 请只把这个本次新增的 interface 改成多行契约,并将 @internal 独立成 tag;无需整理未修改的相邻声明或新增文档抽象。

架构、熵增与测试治理

本轮接受作者说明后,Analyzer 的 definite-only diagnostic policy 与 compiler 的 conservative emission policy 不再视为平行真相:权威事实仍只有一份 structMemberOwnerIssues,certainty 允许两个职责不同的 consumer 机械选择策略,且公开 API 没有承诺 clean analysis 等于 backend 一定生成。

剩余熵增集中在 owner candidate projection。相较上一 HEAD 的 runtimeFallbackReferences 单表,本提交新增 directMemberOwnerReferences、referenceResolutionSnapshots 和 replacementMemberOwnerPath 三段表示,再经 path grouping 与 certainty 转换;它同时漏掉 formal→actual / nested replacement,又把独立定义合成冲突。应保留 macro definition / binding / branch + exact symbol resolution → per-emitted-occurrence owner → ShaderCoreInfo role / canonical issue → Analyzer policy与compiler admission 这一条事实链,具体删除跨定义 path union,并让 parser-owned substitution 投影补齐函数式与嵌套 expansion;不要再增加第三份 macro state、backend wrapper、镜像 role 或第二条校验路径。

测试侧,same-scope incompatible/compatible 与直接 expression-macro 三项回归正确关闭了上一轮反例,应保留;但仍缺 function-like、nested replacement 和“互斥定义各自可 lowering”的公开 artifact 矩阵。应补写这些用例;失效的 path-level 拒绝 fixture 应按新公开契约重写或删除,永远不得为了旧测试在生产代码保留 compatibility branch、legacy fallback、wrapper、镜像状态或第二条转换/校验路径。

- Bind function-like and nested macro owners to emitted reference identities.

- Keep mutually exclusive replacements independent and block unresolved lowering.

- Add GLES100/300 analyzer, live, offline, and runtime regressions.

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(1 个 P1,另有 1 个 P2)。已基于 2b1fb1a13ddafdb314ce9a47d282e4c6ab5f5c80...2a02367588ee1565e1d40a2496a77a2ebffe9069 的 1 个增量 commit、6 个文件完成审查,并沿 macro definition / formal→actual binding / macro result identity / call-site member occurrence → MacroReference / referenceResolutionSnapshots → ShaderCoreInfo canonical owner issue → ShaderIOValidator / ShaderCompilation policy → CodeGenVisitor / runtime expansion 向上游和下游各追一层。目标源码快照的全量 module + type build 成功;本提交新增的 6 个 owner 回归在 node runner 中验证 analyzer/compiler/artifact assertions 为 6/6 通过,GitHub 12 项 checks 也全部成功。默认 browser runner 仍因 macOS sandbox 权限无法启动,未产生可归因于代码的失败;公开 API 反向探针则确认 call-site member 形态在三条 pipeline、两个 GLES target 中都会生成引用未声明 v 的 artifact。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 2a02367588ee1565e1d40a2496a77a2ebffe9069。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮函数式 replacement 内的 formal→actual 漏投影,以及 nested expression/function macro 不递归的问题,已由 formalParameterIndexappendSubstitutedOwnerReferences 和嵌套 referenceSymbols2a023675 中关闭。原反例 GET_UV(v)OWNER_UV 与两层 FORWARD_UV(v) 现在都会进入同一 canonical issue,三条 pipeline、两个 target 一致拒绝。
  • 上轮跨互斥宏定义按字符串 replacementMemberOwnerPath 合并的 false positive 已关闭。字符串 path 已替换为 definition-owned VariableIdentifier AST identity;USE_IO 两个定义现在独立 lowering,false arm 保留 ordinary.uv、true arm 生成 uv
  • 上轮 ShaderStructMemberOwnerIssue 单行 TSDoc 已改为多行说明并将 @internal 独立成 tag;本轮新增的两处单行注释问题见问题 2,不重复前一项。
  • unknown 在 Analyzer 中 silent、在 emitter 中 conservative blocking 的 policy 分层继续按作者解释保持关闭;canonical fact 仍只有一份 structMemberOwnerIssues,没有恢复两份 admission verdict。
  • 更早的 same-scope candidate union、直接 #define OWNER_UV v.uv、parser semantic common gate、prefix/postfix increment、跨独立条件组 exclusivity、复杂条件 global fallback、Particle 派生宏保活、共享预处理表达式与逻辑续行、opaque handoff、source-map 和 pack/install consumer 等关闭项均未被本增量恢复。

问题

  1. [P1] packages/shader-parser/src/parser/AST.ts:1852-1866,2588-2599,2718-2735 / packages/shader-parser/src/ir/ShaderCoreInfo.ts:206-239 / packages/shader-compiler/src/codeGen/CodeGenVisitor.ts:54-86,148-184 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:1086-1305 — 新绑定只覆盖“成员发生在 replacement 内”,成员发生在调用点、宏只返回 owner 时仍没有 value-owner 投影,三条公开 pipeline 会生成 undeclared v. MacroCallFunction.semanticAnalyze 现在已经把 OWNER(v) 的实参 v 放进 referenceSymbols,但 VariableIdentifier.resolvedValueSymbols() 对任意 AST-backed macro 一律返回空;同时 function-like define 在 _analyzeForCodegen 中必然令 aliasesNonBuiltinIdent=false,所以 collectStructMemberOwnerIssues 的 direct-member 分支不会消费刚绑定出的 exact actual。对象式 bare identity 虽能进入该分支,也只按单一 IO role 判为无冲突,没有被标成 runtime-expanded owner。下游拿不到 macro result 的 role,只能保留调用文本。

    在目标 HEAD 对 #define OWNER(x) x + OWNER(v).uv、括号版、#define OWNER v + OWNER.uv 以及两层 wrapper 做了公开 API 探针:Analyzer diagnostics 为空,analyzer handoff、live compiler、offline precompiler 在 GLSLES100/300 全部成功;runtime expansion 后完整 fragment 分别含 v.uv(v).uv,而全局 Varyings v 已被 flatten/delete,产物中没有 v 声明。这与本轮已关闭的 #define GET_UV(x) x.uv 不同:后者的 member occurrence 位于 definition replacement,前者的实际 emitted member occurrence 位于 call site。

    请保留 parser 的 MacroDefine.valueExpression + formal→actual binding + branch identity 为 macro result identity 的唯一 owner、ShaderCoreInfo.structVariableRoles 为 IO role 的唯一 owner:让 resolvedValueSymbols()(或同级唯一 value-owner fact)对可证明的 bare/formal/nested identity replacement 机械返回 bound actual symbols,并让 direct member occurrence消费这份事实;具体删除 owner gate 对 hasAstValue && aliasesNonBuiltinIdent 这一形态启发式的依赖。可证明的单一 IO owner可直接 lowering 为 uv,其余无法安全降低的形态进入现有 canonical issue;不要在 backend 按宏名猜 role、保留 v 镜像声明或新增 compatibility branch。回归应加入 function/object/nested identity 的 ... .uv、ordinary control,覆盖三条 pipeline、两个 target 与 runtime artifact。

  2. [P2] packages/shader-parser/src/parser/AST.ts:135 / packages/shader-parser/src/ir/ShaderCoreInfo.ts:242 — 本提交新增的两条单行 // 注释仍以句号结尾。 请只移除这两处末尾句号;无需改写为新文档抽象或整理未修改的相邻注释。

架构、熵增与测试治理

本轮总体是减熵:跨定义字符串 path 已删除,replacement owner 改由 definition-owned AST identity 表达;formal index 与 runtime-expansion provenance 使函数实参与 nested replacement 复用 parser 的 branch/exact-symbol resolution,canonical backend issue 仍只有一份,Analyzer 与 compiler 也没有新增 backend resolver、镜像 role、legacy fallback 或 compatibility wrapper。新增 kind 只是同一 issue 的机械原因投影,不构成第二份 verdict。

剩余 P1 表明 per-emitted-occurrence 链路仍分成两种 owner 协议:replacement 内成员走 replacementMemberOwner,调用点成员落在 macro result 上时则继续依赖 aliasesNonBuiltinIdent 与“AST macro 的 resolvedValueSymbols() 恒空”这条旧边界。于是同一 formal→actual exact fact 已存在,却没有流到相邻 direct-member consumer。应保留 macro definition/binding/branch → exact value owner → emitted member occurrence → struct role / canonical issue → analyzer/compiler policy 这一条事实链,删除 call-site 的形态启发式,而不是增加第三份 macro state或 backend 同步层。

测试侧,本提交的 function-body member、nested expression/function、unknown、ordinary 与互斥 definition 六项回归正确替换了上一轮缺口,均应保留;没有发现为了旧 fixture 新增生产 compatibility 路径。但矩阵只把 member 放在 replacement 内,没有覆盖 identity macro result 后再取 member,因而全绿仍漏掉上述 artifact。请按新公开 value-owner 契约补写 call-site fixture;不得为旧成功结果保留 wrapper、镜像声明、legacy fallback 或第二条转换/校验路径。

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫧 尘小沫

结论

阻塞级别:Request changes(1 个 P1)。已基于 2a02367588ee1565e1d40a2496a77a2ebffe9069...5ecc305ce6ecc9d9b4e8214cde7a223a27e06b85 的 1 个增量 commit、5 个文件完成审查,并沿 macro definition / formal→actual binding / branch identity → referenceSymbols / isValueIdentity / resolvedValueSymbols / referenceResolutionSnapshots → ShaderCoreInfo canonical owner issue → ShaderIOValidator / ShaderCompilation → CodeGenVisitor / analyzer handoff / live compiler / offline precompiler 向上游和下游各追一层。目标源码快照的全量 module + type build 成功,MacroBranchRuntime.test.ts 的 node runner 为 48/48 通过(WebGL context 分支未执行);公开 API 正反例探针覆盖三条 pipeline 与 GLSLES100/300,确认上一轮无条件 identity 反例已正确 lowering,但穷尽条件定义的合法 identity 变体只在 live compiler 被新增 gate 拒绝。远端当前仅有 labeler check 成功,PR 仍为 CONFLICTING。实际 review 动作为 REQUEST_CHANGES,目标 HEAD 为 5ecc305ce6ecc9d9b4e8214cde7a223a27e06b85。自动 CR 不替代人工 Reviewer 的合入门禁(APPROVE)。

已关闭问题清单

  • 上轮 call-site member occurrence 缺少 value-owner 投影的具体 P1 已由 isValueIdentity、formal→actual substitution、_valueSymbols 与 definition-owned resolution snapshot 在 5ecc305c 中关闭:OWNER(v).uv、括号版、对象式 identity 及两层 function/object wrapper 现在都在三条 pipeline、两个 target 中 lowering 为 uv,runtime artifact 不再引用已删除的 v
  • 上轮 AST.tsShaderCoreInfo.ts 两处新增单行注释句号已移除,P2 已关闭;本增量新增/修改的注释与 TSDoc 未发现同类违规。
  • replacement 内的 formal→actual / nested owner 投影,以及跨互斥定义按字符串 path 合并的 false positive 继续保持关闭:replacement owner 仍由 definition-owned AST identity 分组,没有恢复 replacementMemberOwnerPath、backend name guessing、镜像声明或 compatibility fallback。
  • unknown owner 在 Analyzer 中保持 silent、在 emitter 中保守阻断的职责分层继续按作者解释关闭;更早的 branch/liveness、parser common admission、preprocessor expression、opaque handoff、source-map、pack/install consumer 等关闭项未被本增量恢复。

问题

  1. [P1] packages/shader-parser/src/ir/ShaderCoreInfo.ts:325-359,482-489 / tests/src/shader-compiler/MacroBranchRuntime.test.ts:1169-1400 — 新 member-availability gate 把互斥 identity definitions 聚合后要求 runtime resolver 证明 sibling-arm union,合法穷尽变体因此只在 live compiler 被拒绝。 每个 ReferenceResolutionSnapshot 已分别携带当前 MacroDefine.valueExpression + call-site branch,但 classifyIOMemberAvailability 又把所有 resolution 的 ownerBranches/memberBranches 合成列表,并相对未加 guard 的 reference._branch 做一次 coverage。Analyzer 的完整 BranchAnalysis 能证明同组 #ifdef/#else 覆盖,所以 handoff/offline 通过;默认 getRuntimeBranchCoverage 只在某一个 candidate 单独对 call site 词法可见时返回 covered,无法用两个 sibling arms 的 union 得出覆盖,最终制造 incompatible-io-member/unknown,而 compiler 对任意 canonical issue 都返回 undefined

    在目标 HEAD 验证了两个合法控制:对象式两 arm 分别为 #define OWNER a/b,其中 ab 都是含 uvVaryings;函数式两 arm分别为 #define OWNER(x) x/(x),调用 OWNER(v).uv。两者在 GLSLES100/300 均为 Analyzer diagnostics 空、ShaderCompiler.generate 与 offline precompile 成功并生成闭合的 uv artifact,唯独 live _parseShaderPass 返回 undefined;直接以默认 runtime facts 重建 ShaderCoreInfo 可见唯一 issue 正是 incompatible-io-member/unknown。现有成功矩阵只有无条件 identity,条件矩阵又全部是应拒绝用例,所以 48/48 绿测没有覆盖这个 false positive。

    请保留 MacroDefine.valueExpression + formal→actual binding + branch identity → ReferenceResolutionSnapshot 为 value owner 的唯一事实、structVariableRoles 为 IO role 的唯一事实:按每个 definition-owned resolution 相对其自己的 callSiteBranch 检查所选 IO symbol 的 struct/member 是否可用,再机械汇总结果;删除当前跨 resolution 的 ownerBranches/memberBranches → reference._branch 二次 union coverage。这样 runtime 只消费自己能证明的词法事实,不需要复制 Analyzer solver。回归应增加上述 object/function safe sibling-arm controls,覆盖 handoff/live/offline、两个 target 与 runtime artifact,同时保留现有 mixed/incompatible/opaque 拒绝用例;不要新增 runtime solver、compatibility branch、wrapper 或第二条 admission gate。

架构、熵增与测试治理

本增量对上一轮主链路总体是减熵:call-site 与 replacement 内成员现在都从 parser-owned macro definition、formal binding、branch identity 和 exact symbol resolution 单向派生;resolvedValueSymbols 只为可机械证明的 bare/formal/nested identity 暴露 owner,backend 继续只消费 structVariableRoles 与一份 structMemberOwnerIssues,没有新增 name-based resolver、镜像 role、legacy fallback 或第二份 verdict。isValueIdentity_valueSymbols 与新 issue kind 分别是同一 provenance 在 binding、codegen 和 admission 边界的机械投影,不构成独立状态机。

剩余 P1 的熵增来自新 classifier 把已经按 definition 分开的 snapshot 再聚合成全局 coverage,并让同一 canonical fact依赖两种证明能力:Analyzer/handoff/offline 的完整 branch solver 与 live runtime 的单-candidate lexical resolver因而产生 pass/pass/reject 三态。应保留 per-definition snapshot owner、删除这次 aggregate union 转换,使数据流收敛为 definition/binding → per-resolution member fact → canonical issue → mechanical consumers,而不是给 runtime 增加第三套宏证明。测试新增的 unconditional identity、ordinary、mixed、incompatible 与 opaque fixtures 都应保留;没有发现为了旧 fixture 新增生产 compatibility 路径,但必须补一个合法 sibling-arm control,防止拒绝矩阵继续把 false positive 隐藏在全绿结果中。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation shader Shader related functions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants