@@ -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).
235242fn 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