Skip to content

Commit 79deb9f

Browse files
authored
Fix ConvertUBOToPushConstant: The pointer type instruction needs to be reordered (#743)
The pointer type instruction needs to be reordered, or the SPIRV might be rejected by Vulkan validation layer due to violating SPIR-V rules.
1 parent dd2dfb7 commit 79deb9f

File tree

1 file changed

+88
-5
lines changed

1 file changed

+88
-5
lines changed

Graphics/ShaderTools/src/ConvertUBOToPushConstant.cpp

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,16 @@ class ConvertUBOToPushConstantPass : public spvtools::opt::Pass
225225
return Status::Failure;
226226
}
227227

228-
// Note: We don't need to manually reorder the pointer type instruction.
229-
// SPIR-V does not require types to appear in any specific relative order,
230-
// as long as they are valid type declarations. FindPointerToType handles
231-
// type creation correctly, and the SPIRV-Tools framework manages instruction
232-
// ordering appropriately.
228+
// IMPORTANT: FindPointerToType() may create a new type instruction at the end of
229+
// the types_values section, or it may return an existing type. In either case,
230+
// SPIR-V requires all IDs to be defined before use (SSA form).
231+
//
232+
// We must ensure the pointer type instruction appears BEFORE the OpVariable
233+
// that will reference it. However, we must NOT move an existing type that is
234+
// already correctly positioned, as that could break other instructions that
235+
// depend on it being defined before their use.
236+
spvtools::opt::Instruction* new_ptr_type_inst = get_def_use_mgr()->GetDef(new_ptr_type_id);
237+
EnsureTypeBeforeUseInTypesValues(new_ptr_type_inst, target_var);
233238

234239
// Update the variable's type to the new pointer type
235240
target_var->SetResultType(new_ptr_type_id);
@@ -276,6 +281,76 @@ class ConvertUBOToPushConstantPass : public spvtools::opt::Pass
276281
}
277282

278283
private:
284+
// Returns true if instruction 'a' appears before instruction 'b' in module->types_values().
285+
// This is used to check if a type definition comes before its use in the SPIR-V module.
286+
bool ComesBeforeInTypesValues(const spvtools::opt::Instruction* a,
287+
const spvtools::opt::Instruction* b) const
288+
{
289+
if (a == nullptr || b == nullptr)
290+
return false;
291+
if (a == b)
292+
return true; // Same instruction, no reordering needed
293+
294+
bool seen_a = false;
295+
for (auto& inst : context()->module()->types_values())
296+
{
297+
if (&inst == a)
298+
{
299+
seen_a = true;
300+
}
301+
else if (&inst == b)
302+
{
303+
return seen_a;
304+
}
305+
}
306+
// If either is not in types_values(), be conservative and return false.
307+
return false;
308+
}
309+
310+
// Ensures 'type_inst' is placed before 'use_inst' in the types_values section.
311+
// Only moves the instruction when type_inst currently appears after use_inst.
312+
// This is necessary because SPIR-V requires all IDs to be defined before use (SSA form).
313+
//
314+
// IMPORTANT: This function only moves type_inst forward (earlier in the list), never backward.
315+
// If type_inst is already before use_inst, no action is taken to avoid breaking existing uses.
316+
//
317+
// If use_inst is not in types_values (e.g., it's in a function body), no action is taken
318+
// because the entire types_values section appears before any function in the module.
319+
void EnsureTypeBeforeUseInTypesValues(spvtools::opt::Instruction* type_inst,
320+
spvtools::opt::Instruction* use_inst)
321+
{
322+
if (type_inst == nullptr || use_inst == nullptr)
323+
return;
324+
325+
// Check if use_inst is in types_values section.
326+
// If it's not (e.g., it's in a function body like OpAccessChain),
327+
// we don't need to move type_inst because the entire types_values section
328+
// appears before any function in the module.
329+
bool use_in_types_values = false;
330+
for (auto& inst : context()->module()->types_values())
331+
{
332+
if (&inst == use_inst)
333+
{
334+
use_in_types_values = true;
335+
break;
336+
}
337+
}
338+
339+
if (!use_in_types_values)
340+
return;
341+
342+
// If type_inst already comes before use_inst, do nothing.
343+
// This is critical: moving an existing type that's already correctly positioned
344+
// could break other instructions that depend on it.
345+
if (ComesBeforeInTypesValues(type_inst, use_inst))
346+
return;
347+
348+
// type_inst appears after use_inst (or not found), so we need to move it.
349+
// Insert it immediately before the use_inst to satisfy the SSA requirement.
350+
type_inst->RemoveFromList();
351+
type_inst->InsertBefore(use_inst);
352+
}
353+
279354
// Recursively updates the storage class of pointer types used by instructions
280355
// that reference the target variable.
281356
bool PropagateStorageClass(spvtools::opt::Instruction& inst, std::unordered_set<uint32_t>& visited)
@@ -380,6 +455,14 @@ class ConvertUBOToPushConstantPass : public spvtools::opt::Pass
380455
if (new_result_type_id == 0)
381456
return;
382457

458+
// Ensure the pointer type is properly positioned in the types section.
459+
// FindPointerToType may return an existing type or create a new one at the end.
460+
// If inst is in types_values (e.g., OpConstantNull), we need to ensure the type
461+
// is defined before inst. If inst is in a function body (e.g., OpAccessChain),
462+
// no reordering is needed since types_values always precedes functions.
463+
spvtools::opt::Instruction* new_type_inst = get_def_use_mgr()->GetDef(new_result_type_id);
464+
EnsureTypeBeforeUseInTypesValues(new_type_inst, &inst);
465+
383466
inst.SetResultType(new_result_type_id);
384467
context()->UpdateDefUse(&inst);
385468
}

0 commit comments

Comments
 (0)