Skip to content

Commit 3950c08

Browse files
committed
Fix duplicate IDs for OpFunctionParameter in assembler
The assembler used Builder::function_parameter() which auto-generates IDs from Builder's internal next_id counter. This counter is separate from the module_builder's next_numeric_id counter used by all other instructions, leading to ID collisions (e.g., OpFunction and its OpFunctionParameter both getting the same result ID). Fix by pre-allocating the ID through module_builder.bind_typed_result() and constructing the OpFunctionParameter instruction manually, matching the pattern used by all other translate methods.
1 parent d8c6037 commit 3950c08

File tree

1 file changed

+112
-10
lines changed

1 file changed

+112
-10
lines changed

rust/spirv-tools-core/src/assembly/assembler.rs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,16 +1371,27 @@ impl<'a> AssemblyTranslator<'a> {
13711371
return;
13721372
};
13731373

1374-
let type_id = self.module_builder.resolve_type_id(result_type);
1375-
match self.builder.function_parameter(type_id) {
1376-
Ok(parameter_id) => {
1377-
self.module_builder.bind_result_id(result_id, parameter_id);
1378-
self.module_builder
1379-
.note_numeric_result_type(parameter_id, type_id);
1380-
self.record_function_param();
1381-
}
1382-
Err(error) => self.emit_builder_error(error, opcode_pos),
1383-
}
1374+
let Some(selected_function) = self.builder.selected_function() else {
1375+
self.emit_builder_error(BuildError::DetachedFunctionParameter, opcode_pos);
1376+
return;
1377+
};
1378+
1379+
// Use bind_typed_result to allocate IDs from the module_builder's counter,
1380+
// rather than Builder::function_parameter() which uses Builder's separate
1381+
// next_id counter and can produce duplicate IDs.
1382+
let (type_id, result_id) = self
1383+
.module_builder
1384+
.bind_typed_result(result_type, result_id);
1385+
let inst = dr::Instruction::new(
1386+
spirv::Op::FunctionParameter,
1387+
Some(type_id),
1388+
Some(result_id),
1389+
vec![],
1390+
);
1391+
self.builder.module_mut().functions[selected_function]
1392+
.parameters
1393+
.push(inst);
1394+
self.record_function_param();
13841395
}
13851396

13861397
fn translate_label(&mut self, instruction: &ParsedInstruction<'a>) {
@@ -6245,4 +6256,95 @@ OpFunctionEnd"#;
62456256
&dr::Operand::LiteralBit32(42.0_f32.to_bits())
62466257
);
62476258
}
6259+
6260+
#[test]
6261+
fn function_parameter_gets_unique_id_from_function() {
6262+
// Regression test: OpFunctionParameter must get a different result ID
6263+
// than its parent OpFunction. Previously, the assembler used Builder's
6264+
// internal next_id counter (via function_parameter()) which was
6265+
// independent of the module_builder's counter, causing collisions.
6266+
let source = [
6267+
"%void = OpTypeVoid",
6268+
"%uint = OpTypeInt 32 0",
6269+
"%fn_type = OpTypeFunction %void %uint",
6270+
"OpMemoryModel Logical GLSL450",
6271+
"%func = OpFunction %void None %fn_type",
6272+
"%param = OpFunctionParameter %uint",
6273+
"%entry = OpLabel",
6274+
"OpReturn",
6275+
"OpFunctionEnd",
6276+
];
6277+
let parsed: Vec<_> = source
6278+
.into_iter()
6279+
.map(|line| {
6280+
parse_instruction(line)
6281+
.unwrap_or_else(|err| panic!("failed to parse '{line}': {err:?}"))
6282+
})
6283+
.collect();
6284+
let refs: Vec<_> = parsed.iter().collect();
6285+
let module = assemble_instructions(&refs).expect("assemble instructions");
6286+
let function = module.functions.first().expect("function");
6287+
let func_id = function.def.as_ref().unwrap().result_id.unwrap();
6288+
let param = function.parameters.first().expect("parameter");
6289+
let param_id = param.result_id.unwrap();
6290+
assert_ne!(
6291+
func_id, param_id,
6292+
"OpFunction and OpFunctionParameter must have different result IDs, \
6293+
but both got {func_id}"
6294+
);
6295+
}
6296+
6297+
#[test]
6298+
fn multiple_functions_with_parameters_have_unique_ids() {
6299+
// Ensure that across multiple functions, all result IDs are unique.
6300+
let source = [
6301+
"%void = OpTypeVoid",
6302+
"%uint = OpTypeInt 32 0",
6303+
"%fn_type = OpTypeFunction %void %uint",
6304+
"OpMemoryModel Logical GLSL450",
6305+
"%func1 = OpFunction %void None %fn_type",
6306+
"%param1 = OpFunctionParameter %uint",
6307+
"%entry1 = OpLabel",
6308+
"OpReturn",
6309+
"OpFunctionEnd",
6310+
"%func2 = OpFunction %void None %fn_type",
6311+
"%param2 = OpFunctionParameter %uint",
6312+
"%entry2 = OpLabel",
6313+
"OpReturn",
6314+
"OpFunctionEnd",
6315+
];
6316+
let parsed: Vec<_> = source
6317+
.into_iter()
6318+
.map(|line| {
6319+
parse_instruction(line)
6320+
.unwrap_or_else(|err| panic!("failed to parse '{line}': {err:?}"))
6321+
})
6322+
.collect();
6323+
let refs: Vec<_> = parsed.iter().collect();
6324+
let module = assemble_instructions(&refs).expect("assemble instructions");
6325+
let mut all_ids = std::collections::HashSet::new();
6326+
for function in &module.functions {
6327+
let func_id = function.def.as_ref().unwrap().result_id.unwrap();
6328+
assert!(
6329+
all_ids.insert(func_id),
6330+
"Duplicate function ID: {func_id}"
6331+
);
6332+
for param in &function.parameters {
6333+
let param_id = param.result_id.unwrap();
6334+
assert!(
6335+
all_ids.insert(param_id),
6336+
"Duplicate parameter ID: {param_id}"
6337+
);
6338+
}
6339+
for block in &function.blocks {
6340+
if let Some(label) = &block.label {
6341+
let label_id = label.result_id.unwrap();
6342+
assert!(
6343+
all_ids.insert(label_id),
6344+
"Duplicate label ID: {label_id}"
6345+
);
6346+
}
6347+
}
6348+
}
6349+
}
62486350
}

0 commit comments

Comments
 (0)