Local storage epochs and coroutine frame reconstruction
This integrates the unpublished Local/coroutine fixes onto 3485bd89d, not
onto the older dirty checkout. Already-published scheduler, HIP ABI, late-inline,
and SPIR-V changes are not reverted or duplicated. Evidence is retained in
/var/tmp/luisa-publish-coro-bQIjua.
Storage contract
Ordinary DSL Var<T> values, including scalars, vectors and fixed Array,
retain their default-zero semantics. Local<T> is uninitialized storage.
Its physical allocation is function-scoped, but every dynamic execution of
its lexical declaration starts a fresh storage epoch. AST records that
boundary as an exact local-reference assignment from UNDEFINED.
UNDEFINED is not a zero fill and is not evidence of a program definition.
Direct AST code generators omit this exact whole-local assignment; partial
assignments, nonlocal writes and ordinary ZERO expressions are unaffected.
Coroutine scope analysis consumes the epoch before removing the seed. Retaining
the old physical bits is a valid refinement of an arbitrary value, not a
promise that those bits may be read as initialized state.
CoroFrame::create no longer clears all fields. Entry/resume protocols own the
fields live in that state. CoroSlotAccess::_read reconstructs the complete
static leaf partition of a binding and therefore does not need a preceding
aggregate clear. Radix-sort local keys and ranks are explicitly assigned for
every item before any read, so their scratch arrays use Local as well.
Generic scope proof
For payload P, same-sized unsigned tag array T and counter C, the proof tracks
the possible tags of undefined records in [0,C), a separate tail record,
and the pending physical record at C. A read needs a valid index and a tag
constraint disjoint from every applicable unsafe set. Publication, saved
allocation tickets, conditional writes, reset, and nonwrapping rollback are
ordinary XIR state transitions. Rollback removes semantic membership but
does not erase an already-written physical record.
Unsafe sets use May union at CFG joins; definite index/definition facts use Must intersection. Boolean assignments and edge constraints act on a common finite valuation domain. Unknown aliases, unrecognized counter mutations, stale discriminator observations and insufficient range/definition evidence cannot justify contraction. No renderer name, scene profile, noinline marker, or application-supplied lifetime assertion enters this proof.
Earlier alloca moves may relocate stores in a companion array/scalar. Each discriminated candidate therefore snapshots the current instruction order, not a stale pass-entry order. The existing scalar snapshot query uses a cached two-state Clean/Dirty dataflow solution. Backward relation liveness uses a worklist for the same finite monotone equations as the previous full scans.
Two defects caught during integration
The initial candidate did not pass its negative rollback regression:
vector<uint64_t>{word_count, 0} selects the initializer-list constructor.
It creates two elements and treats bits of word_count as initialized payload
and tag indices. The intended Must lattice bottom is an N-word zero bitset;
the fix uses vector(count, value) construction. The permanent static-index
witness checks capacities on both sides of word boundaries and also requires
that the discriminated domain was actually exercised.
After correcting that error, the positive rollback witness exposed premature
Boolean projection. A branch can be dead as a direct SSA use while still
relating a stored flag to a conditional payload definition. In general,
exists b (F(b) and G(b)) is not equivalent to projecting b independently
from F and G. This product retains the shared valuation rather than applying
direct-use liveness to each component separately. Dynamic definitions still
kill the previous Boolean value; unsafe reads remain rejected.
prefix-candidate.log records the original false acceptance.
prefix-minimal-green.log records the positive witness failing after only
the bitset correction. prefix-product-green.log records both directions
passing after preserving the relation. Counterfactual and final logs are
separate; no counterfactual implementation is published.
Callable boundary contract
A recursively read-only reference can be replaced by a call-site snapshot only when every actual is thread-local and is provably disjoint from every writable reference actual. Distinct local-allocation roots suffice; a shared root, unknown pointer, external signature constraint or root callable does not. Writable arguments remain references. Promotion runs before the ordinary callable/coroutine pass-domain boundary, preserving rematerializable SSA captures without requesting inlining or noinline. XIR-to-AST also preserves function names through this round trip.
The unmodified upstream implementation is built separately under
build-baseline. New tests use only test-local compatibility inspection for
the missing AST predicate and diagnostic counter; production baseline code
is unchanged. Observable allocation/frame assertions remain unchanged.
The root-callable regression guards subsequent pointer inspection after a
failed identity assertion so old code reports failures instead of crashing.
Validation scope
The independent SDK enables HIP, fallback and native XIR-to-SPIR-V Vulkan, with DXC compatibility disabled at configure time. All builds use 32 threads. The whole Psycles validation snapshot includes its current tracked changes and the two required microfacet source files, and uses only this SDK’s headers, generated configuration and rebuilt libraries. It excludes profiling dumps and the unfinished volume-oracle work.
The full initial CTest run also exposed three unrelated failures: the HLSL
validation executable requires DXC despite this native-only configuration;
test_spirv_xir_dialect and test_spirv_target_feature_codegen reproduce with
the unmodified baseline AST/XIR/core libraries and unchanged SPIR-V code.
Those failures are not counted as successful validation or silently fixed by
changing shader semantics.
Final verification
The final prefix regression passes 26 cases / 361 assertions. Its static-index
witness covers capacities 1, 4, 64, 65, 128 and 255, with a separate publication
block so constant forwarding cannot bypass candidate discovery. Reintroducing
only the erroneous bitset constructors produces 14 assertion failures in two
cases (prefix-final-counterfactual-red.log); restoring the fix passes
(prefix-publication-green.log). No counterfactual code remains in the build.
Validation |
Result |
|---|---|
SDK focused host CTest |
151/151; excludes the three failures above and two render tutorials |
SDK HIP runtime executables |
7/7 |
SDK fallback runtime executables |
7/7 |
SDK strict native Vulkan runtime executables |
6/6 |
Whole Psycles host CTest |
120/120; source-size and external Blender tests excluded |
Whole Psycles HIP CTest |
168/168, serial GPU execution |
Whole Psycles fallback CTest |
170/170 |
Whole Psycles strict native Vulkan focused CTest |
12/12 |
The seven SDK runtime executables are test_coro_frame_runtime,
test_coro_all_schedulers, test_coro_radix_sort, test_counted_local_array,
test_coro_wavefront_auxiliary_pipeline,
test_coro_wavefront_resume_annotation, and
test_coro_wavefront_auxiliary_admission. Vulkan runs the same set except
radix sort. The frame executable includes the new ordinary scalar/vector/Array
default-zero regression (306 assertions in 22 cases on each backend).
The first parallel HIP CTest run was interrupted after 133 reported passes while 32 test processes remained active without further output. The subsequent serial run passed all 168. The interrupted run is not counted as a pass, and its cause has not been established.
On this AMD-only runtime, configure also uses
LUISA_COMPUTE_ENABLE_VK_CUDA_INTEROP=OFF: that optional feature is independent
of the disabled CUDA backend and otherwise links the toolkit driver stub,
leaving an unsatisfied libcuda.so.1 runtime dependency. No source change or
fake driver is needed. Native Vulkan is configured with
LUISA_COMPUTE_ENABLE_VK_XIR_SPIRV=ON,
LUISA_COMPUTE_ENABLE_VK_AST_LLVM_SPIRV=OFF, and
LUISA_COMPUTE_VULKAN_ENABLE_DXC_COMPATIBILITY=OFF. Runtime gates are:
LUISA_VULKAN_USE_XIR=1
LUISA_VULKAN_REQUIRE_NATIVE_XIR_SPIRV=1
LUISA_VULKAN_DISABLE_DXC=1
LD_DEBUG=libs
Both SDK and whole-renderer canaries compile native SPIR-V. Loader logs contain no DXC/DXIL loading attempts; the rebuilt Vulkan library has no CUDA driver dependency. The map-range whole-scene canary passes at 16x16 / 4 spp on fallback and strict native Vulkan, with all 46 output channels finite.
The rebuilt SDK also completes uncached HIP staged-wavefront canaries:
Scene |
Resolution / samples |
Render time |
Coroutine frame |
|---|---|---|---|
Lone Monk |
1440x1080 / 256 spp |
13.7828 s |
55 fields / 220 bytes |
Monster |
1080x1080 / 256 spp |
14.9817 s |
71 fields / 284 bytes |
Both have 46 finite output channels. These are single canary measurements,
not a paired performance benchmark. Against the recorded Cycles 5.2.1 HIP
reference, Lone Monk Combined relative RMSE is 0.0124035 and DiffInd relative
RMSE remains 0.128819. The remaining renderer-parity gap is not claimed fixed.
Detailed evidence is in whole-psycles-*.log, final-runtime-*.log,
final-native-vk-*.log, map-native-vk.log, monk-hip.log,
monster-hip.log, and monk-comparison.json under the evidence directory above.