Shared switch bodies across representation and CFG lowering
Several labels can now name one owned AST case body, matching the existing
XIR relation add_case(label, block). $case(a, b, c) and the case builder’s
host integral span overload record that body once. The runtime still selects
exactly one body; no classifier, callable, or state copy is introduced.
Representation invariant and first counterexample
Let a switch map distinct literals to target blocks. Labels with the same target are parallel incoming edges to one case construct, not separate copies of its effects. The previous XIR-to-AST translation copied the body once per label; AST-to-XIR then created separate blocks. The permanent three-label, one-buffer-write roundtrip regression failed three assertions before repair.
SwitchCaseStmt retains its single-label API and stores additional labels
only for a group. A new statement tag is appended, preserving existing tag
values, single-label hashes and the old binary record. Multi-label records
have an explicit count. JSON groups use values; the existing JSON int32
literal format is not silently widened. Binary/XIR paths cover wide labels.
Duplication, serialization, traversal, all AST code generators and both XIR
translations preserve one body. XIR-to-AST grouping uses first-occurrence
order and an O(N) target-to-group map, never hash iteration order. A shared
default/case body is still emitted separately by XIR-to-AST; this change does
not claim to remove that additional duplication.
Single-label byte-idempotence across serialization was not assumed: that property also failed for the old representation. The controls instead verify decoded labels/body, recomputed duplicate hash, repeated loading, and equal hash/binary output for the old scalar builder versus a one-element span at the same source location.
Shared targets are not distinct construct entries
The first native Vulkan regression exposed a separate defect. A five-block,
ten-instruction switch with three labels sharing one write body was expanded
by split_switch_cases: distinct label proxies entered the shared body, so
neither proxy dominated that payload. Exit repair then misclassified the new
cross-case entries, introduced an artificial cycle and left an unstructured
conditional. The failed module had grown to 21 blocks / 36 instructions.
The OpSwitch specification
requires distinct literal values, not distinct target labels. The
maximal-reconvergence extension
also explicitly discusses multiple labels for one case construct. Removing
the unconditional target-uniqueness normalization preserves that relation;
genuine cross-construct entry enforcement remains enabled. There is no
case-specific exception in fixup_construct_exits or validator bypass.
An independent test builds this CFG directly with the existing XIR API. Its 48 shapes cover signed/unsigned 32-/64-bit selectors, raw/structured input, aliased/separate default, and one/two/three shared labels. Before the repair, 40 assertions failed; afterwards, all 552 pass. The tests require valid output, shared target identity, preserved observable writes and no introduced loop. Two old tests were updated because they explicitly required the now-removed proxy blocks; their effect, target and verifier checks remain. The broader outstanding proof obligations in the loop-epoch audit are not claimed complete by this repair.
Narrow signed physical literals
After the CFG fix, signed/unsigned 64-bit native kernels passed, but signed
16-bit cases failed SPIR-V validation. XIR’s canonical 0x8000 / 0xffff
must be encoded in a physical 32-bit literal word as 0xffff8000 /
0xffffffff. The old single-label spelling reproduced the same failure.
The generic correction sign-extends signed 8-/16-bit literals at physical
OpSwitch emission, after canonical-bit validation. Unsigned and 32-/64-bit
encoding, XIR semantics and device arithmetic are unchanged.
Permanent and whole-application validation
test_xir2ast_translators: shared-body roundtrip red/green.test_switch_case_group: variadic/span DSL, filtered label sets, duplicate, JSON, binary, XIR and single-label compatibility controls.test_xir_restructure_shared_switch: all 48 CFG shapes / 552 assertions.test_switch_case_group_runtime: 211 assertions on HIP, fallback and strict native Vulkan. Covers separate and grouped signed/unsigned 8-/16-/64-bit labels, default, loop break/continue, early return, and StateMachine plus Wavefront coroutine suspension. No failing type is excluded.Full 32-thread build and 133/133 CTests selected by
-L '^unit'inbuild-tests-hip; the narrower exactunitlabel selects 132/132.
The Vulkan run sets LUISA_VULKAN_USE_XIR=1,
LUISA_VULKAN_REQUIRE_NATIVE_XIR_SPIRV=1 and LUISA_VULKAN_DISABLE_DXC=1.
Loader diagnostics show no DXC/DXIL library load. The HIP-only child build
uses its own ABI-matched runtime; fallback/Vulkan use a separate executable
linked against the application’s SYSTEM_STL=ON libraries, not mixed plugins.
The full unchanged Barbershop input also completes A/control/B/B/A renders
at 2048x858 / 64 spp with all 46 channels finite. Grouping the application’s
original-Cycles BSDF labels reduces its main function from 184,759 to 159,801
bounded machine instructions, without changing the four-function set,
49 call sites, 416-byte coroutine frame or 256 VGPRs. Surface medians are
5.860 / 5.809 seconds: only a small measured change, not efficiency parity.
Restoring only the old application case spelling restores byte-identical
baseline .text, with the generic compiler fixes still present.
Local evidence: /var/tmp/psycles-shared-case-Btjjlq. Primary records are
formal-analysis.md, roundtrip-red.log, cfg-shared-target-analysis.md,
cfg-red.log, cfg-green.log, spirv-literal-analysis.md,
runtime-vk-narrow-red.log, runtime-hip-final.log,
runtime-fallback-final.log, runtime-vk-green.log,
child-full-build-final.log, child-unit-complete.log,
child-unit-complete-expanded.log and profiles.json.
The application owns the complete image/timing report. These primitive tests
and structural controls do not implement a CPU shader oracle.