{"record":{"id":"433b67e10d4a5556","repo":"stride3d/stride","slug":"can-t-write-value-on-input-or-output-struct","errorCode":null,"errorMessage":"Can't write value on input or output struct","messagePattern":"Can't write value on input or output struct","errorType":"validation","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Analysis/ReadWriteAnalyzer.cs","lineNumber":138,"sourceCode":"                if (variables.TryGetValue(accessChain.Base, out var variableInfo))\n                    variableInfo.UsedThisStage = true;\n                if (analysisResult.Resources.TryGetValue(accessChain.Base, out var resourceInfo))\n                    resourceInfo.UsedThisStage = true;\n                if (analysisResult.CBuffers.TryGetValue(accessChain.Base, out var cbufferInfo))\n                    cbufferInfo.UsedThisStage = true;\n            }\n            else if (i.Op is Op.OpStore && new OpStore(ref i) is { } store)\n            {\n                // Check for indirect access chains\n                if (!accessChainBases.TryGetValue(store.Pointer, out var accessChain))\n                    accessChain.Base = store.Pointer;\n\n                if (streams.TryGetValue(accessChain.Base, out var streamInfo))\n                {\n                    var streamKind = accessChain.StreamKind!.Value;\n                    // Write on input/output stream are not allowed\n                    if (streamKind is StreamsKindSDSL.Input or StreamsKindSDSL.Output)\n                        throw new InvalidOperationException(\"Can't write value on input or output struct\");\n\n                    streamInfo.Write = true;\n                }\n                if (variables.TryGetValue(accessChain.Base, out var variableInfo))\n                    variableInfo.UsedThisStage = true;\n                if (analysisResult.Resources.TryGetValue(accessChain.Base, out var resourceInfo))\n                    resourceInfo.UsedThisStage = true;\n                if (analysisResult.CBuffers.TryGetValue(accessChain.Base, out var cbufferInfo))\n                    cbufferInfo.UsedThisStage = true;\n            }\n            else if (i.Op is Op.OpAtomicIAdd or Op.OpAtomicISub or Op.OpAtomicUMin or Op.OpAtomicUMax\n                or Op.OpAtomicSMin or Op.OpAtomicSMax or Op.OpAtomicAnd or Op.OpAtomicOr or Op.OpAtomicXor\n                or Op.OpAtomicExchange or Op.OpAtomicCompareExchange or Op.OpAtomicLoad or Op.OpAtomicStore\n                or Op.OpAtomicIIncrement or Op.OpAtomicIDecrement)\n            {\n                // Atomic operations reference a pointer (word 3 after opcode, i.e. first operand after ResultType+ResultId or just after opcode for OpAtomicStore)\n                // Extract the pointer and mark the underlying resource as used\n                var pointer = i.Op == Op.OpAtomicStore","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Spirv/Processing/Interfaces/Analysis/ReadWriteAnalyzer.cs#L120-L156","documentation":"ReadWriteAnalyzer validates stream (input/output interface) access chains during SPIR-V post-processing. Writing through an access chain whose base is an Input or Output streams struct is illegal at this stage (stage outputs are assembled elsewhere), so the analyzer throws InvalidOperationException.","triggerScenarios":"A shader writes to a variable in the `streams` input or output struct via an OpAccessChain + OpStore inside AnalyzeStreamReadWrites — e.g. assigning to an input stream member or storing directly into the output stream in an unexpected place.","commonSituations":"Shader code doing `streams.Normal = ...` on an input stream, or a pass/transformation writing the output stream where the analyzer doesn't expect a store; mislabeled StreamsKindSDSL decoration on a variable.","solutions":["Remove the store into the input/output streams struct in the shader code","Write outputs only through the stage's declared output mechanism (e.g. the SDSL stage output assignment, not a raw access-chain store)","Check that the variable's StreamsKindSDSL decoration (Input vs Output) matches the intended usage","If this is a compiler transformation emitting the store, fix the transformation to route writes to the correct target"],"exampleFix":"// before (SDSL)\nstreams.Position = position; // writing to input stream\n// after\nvar p = streams.Position; // read only from input streams","handlingStrategy":"validation","validationCode":"if (accessChain.IsStore && streams.TryGetValue(accessChain.Base, out var si) && si.Kind is StreamsKindSDSL.Input or StreamsKindSDSL.Output)\n    throw new InvalidOperationException(\"Store to input/output stream is not allowed\");","typeGuard":"bool IsStreamReadable(StreamInfo s) => s.Kind is StreamsKindSDSL.Input or StreamsKindSDSL.Output;","tryCatchPattern":"try { analyzer.AnalyzeStreamReadWrites(module); } catch (InvalidOperationException ex) { logger.LogError(ex, \"Illegal stream write\"); throw new ShaderCompilationException(\"Input/output streams are read-only at this stage\", ex); }","preventionTips":["Never assign to members of the `streams` input struct in SDSL code","Use the stage's declared output assignment mechanism for outputs","Validate StreamsKindSDSL decorations after every transformation"],"tags":["shader","spirv","streams-analysis"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}