Skip to content

Commit 7a008fe

Browse files
committed
mem2reg: document access chain index requirements and add u64 test
Add doc comment to construct_access_chain_info explaining that access chain indices must be scalar integers per the SPIR-V spec, and that the constants map only tracks u32 (matching what rustc emits). Add test for a u64 constant index that is valid SPIR-V but not resolved by mem2reg, verifying the variable is not promoted.
1 parent 35bf77b commit 7a008fe

File tree

1 file changed

+95
-42
lines changed

1 file changed

+95
-42
lines changed

crates/rustc_codegen_spirv/src/linker/mem2reg.rs

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ struct VarInfo {
232232
indices: Vec<u32>,
233233
}
234234

235+
/// Resolve an `OpAccessChain`/`OpInBoundsAccessChain` into a `VarInfo` with
236+
/// flattened indices. Returns `None` if any index is not a known u32 constant,
237+
/// which makes the parent variable ineligible for promotion.
238+
///
239+
/// Per the SPIR-V spec, access chain indices must be scalar integers
240+
/// (<https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpAccessChain>).
241+
/// The `constants` map only tracks u32 constants (matching what rustc emits).
235242
fn construct_access_chain_info(
236243
pointer_to_pointee: &FxHashMap<Word, Word>,
237244
constants: &FxHashMap<Word, u32>,
@@ -743,6 +750,10 @@ mod tests {
743750
let mut module = load(&bytes);
744751

745752
let mut pointer_to_pointee = FxHashMap::default();
753+
// Only u32 constants are collected — these are used exclusively for
754+
// resolving OpAccessChain indices (which must be scalar integers per
755+
// https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpAccessChain).
756+
// Other constant types (f32, etc.) are store/load values, not indices.
746757
let mut constants = FxHashMap::default();
747758
let mut u32_type = None;
748759
for inst in &module.types_global_values {
@@ -1217,6 +1228,90 @@ mod tests {
12171228
);
12181229
}
12191230

1231+
#[test]
1232+
fn non_constant_access_chain_index_not_promoted() {
1233+
// An OpAccessChain with a runtime (non-constant) index makes the
1234+
// variable ineligible: construct_access_chain_info cannot resolve it.
1235+
let output = run_mem2reg(
1236+
"OpCapability Shader
1237+
OpMemoryModel Logical GLSL450
1238+
OpEntryPoint Fragment %main \"main\" %idx_in %out
1239+
OpExecutionMode %main OriginUpperLeft
1240+
%void = OpTypeVoid
1241+
%float = OpTypeFloat 32
1242+
%float_1 = OpConstant %float 1.0
1243+
%uint = OpTypeInt 32 0
1244+
%arr2 = OpTypeArray %float %uint
1245+
%ptr_func_arr = OpTypePointer Function %arr2
1246+
%ptr_func_float = OpTypePointer Function %float
1247+
%ptr_in_uint = OpTypePointer Input %uint
1248+
%ptr_out_float = OpTypePointer Output %float
1249+
%idx_in = OpVariable %ptr_in_uint Input
1250+
%out = OpVariable %ptr_out_float Output
1251+
%fn_void = OpTypeFunction %void
1252+
%main = OpFunction %void None %fn_void
1253+
%entry = OpLabel
1254+
%var = OpVariable %ptr_func_arr Function
1255+
%idx = OpLoad %uint %idx_in
1256+
%elem = OpAccessChain %ptr_func_float %var %idx
1257+
OpStore %elem %float_1
1258+
%val = OpLoad %float %elem
1259+
OpStore %out %val
1260+
OpReturn
1261+
OpFunctionEnd",
1262+
);
1263+
let has_func_var = output
1264+
.lines()
1265+
.any(|l| l.contains("OpVariable") && l.contains("Function"));
1266+
assert!(
1267+
has_func_var,
1268+
"Variable with dynamic access chain index should not be promoted\n{output}"
1269+
);
1270+
}
1271+
1272+
#[test]
1273+
fn non_u32_constant_access_chain_index_not_promoted() {
1274+
// Access chain indices must be scalar integers per the SPIR-V spec
1275+
// (https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpAccessChain),
1276+
// but the constants map only tracks u32. A u64 constant index is valid
1277+
// SPIR-V but is not resolved by mem2reg, so the variable stays.
1278+
let output = run_mem2reg(
1279+
"OpCapability Shader
1280+
OpCapability Int64
1281+
OpMemoryModel Logical GLSL450
1282+
OpEntryPoint Fragment %main \"main\" %out
1283+
OpExecutionMode %main OriginUpperLeft
1284+
%void = OpTypeVoid
1285+
%float = OpTypeFloat 32
1286+
%float_1 = OpConstant %float 1.0
1287+
%uint = OpTypeInt 32 0
1288+
%ulong = OpTypeInt 64 0
1289+
%ulong_0 = OpConstant %ulong 0
1290+
%arr2 = OpTypeArray %float %uint
1291+
%ptr_func_arr = OpTypePointer Function %arr2
1292+
%ptr_func_float = OpTypePointer Function %float
1293+
%ptr_out_float = OpTypePointer Output %float
1294+
%out = OpVariable %ptr_out_float Output
1295+
%fn_void = OpTypeFunction %void
1296+
%main = OpFunction %void None %fn_void
1297+
%entry = OpLabel
1298+
%var = OpVariable %ptr_func_arr Function
1299+
%elem = OpAccessChain %ptr_func_float %var %ulong_0
1300+
OpStore %elem %float_1
1301+
%val = OpLoad %float %elem
1302+
OpStore %out %val
1303+
OpReturn
1304+
OpFunctionEnd",
1305+
);
1306+
let has_func_var = output
1307+
.lines()
1308+
.any(|l| l.contains("OpVariable") && l.contains("Function"));
1309+
assert!(
1310+
has_func_var,
1311+
"Variable with u64 access chain index should not be promoted\n{output}"
1312+
);
1313+
}
1314+
12201315
#[test]
12211316
fn phi_with_opline_interleaved() {
12221317
// OpLine/OpNoLine can appear between OpPhi instructions per the SPIR-V
@@ -1326,48 +1421,6 @@ mod tests {
13261421
assert_eq!(phi_count, 1, "Expected exactly one OpPhi\n{output}");
13271422
}
13281423

1329-
#[test]
1330-
fn non_constant_access_chain_index_not_promoted() {
1331-
// An OpAccessChain with a non-constant index makes the variable
1332-
// ineligible because construct_access_chain_info cannot resolve it.
1333-
let output = run_mem2reg(
1334-
"OpCapability Shader
1335-
OpMemoryModel Logical GLSL450
1336-
OpEntryPoint Fragment %main \"main\" %idx_in %out
1337-
OpExecutionMode %main OriginUpperLeft
1338-
%void = OpTypeVoid
1339-
%float = OpTypeFloat 32
1340-
%float_1 = OpConstant %float 1.0
1341-
%uint = OpTypeInt 32 0
1342-
%arr2 = OpTypeArray %float %uint
1343-
%ptr_func_arr = OpTypePointer Function %arr2
1344-
%ptr_func_float = OpTypePointer Function %float
1345-
%ptr_in_uint = OpTypePointer Input %uint
1346-
%ptr_out_float = OpTypePointer Output %float
1347-
%idx_in = OpVariable %ptr_in_uint Input
1348-
%out = OpVariable %ptr_out_float Output
1349-
%fn_void = OpTypeFunction %void
1350-
%main = OpFunction %void None %fn_void
1351-
%entry = OpLabel
1352-
%var = OpVariable %ptr_func_arr Function
1353-
%idx = OpLoad %uint %idx_in
1354-
%elem = OpAccessChain %ptr_func_float %var %idx
1355-
OpStore %elem %float_1
1356-
%val = OpLoad %float %elem
1357-
OpStore %out %val
1358-
OpReturn
1359-
OpFunctionEnd",
1360-
);
1361-
// The dynamic index prevents promotion.
1362-
let has_func_var = output
1363-
.lines()
1364-
.any(|l| l.contains("OpVariable") && l.contains("Function"));
1365-
assert!(
1366-
has_func_var,
1367-
"Variable with dynamic access chain index should not be promoted\n{output}"
1368-
);
1369-
}
1370-
13711424
#[test]
13721425
fn no_variables_is_noop() {
13731426
let output = run_mem2reg(

0 commit comments

Comments
 (0)