Skip to content

Commit 712ad0b

Browse files
committed
Add image capability and query validation checks
Add three missing validation checks from the C++ validator: - Int64ImageEXT capability required for 64-bit int sampled types - StorageImageMultisample capability required for multisampled storage images - OpImageQueryFormat/OpImageQueryOrder result type and operand validation
1 parent 4e92160 commit 712ad0b

4 files changed

Lines changed: 333 additions & 0 deletions

File tree

rust/spirv-tools-core/src/validation/error.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4229,6 +4229,16 @@ pub enum ValidationError {
42294229
/// The image type ID.
42304230
type_id: Option<TypeId>,
42314231
},
4232+
/// OpTypeImage with 64-bit int sampled type requires Int64ImageEXT capability.
4233+
#[error(
4234+
"Capability Int64ImageEXT is required when using Sampled Type of 64-bit int"
4235+
)]
4236+
ImageTypeRequiresInt64ImageCapability,
4237+
/// OpTypeImage multisampled storage image requires StorageImageMultisample capability.
4238+
#[error(
4239+
"Capability StorageImageMultisample is required when using multisampled storage image"
4240+
)]
4241+
ImageTypeRequiresStorageImageMultisampleCapability,
42324242
/// OpTypeImage with SubpassData dimension must have Format = Unknown.
42334243
#[error("OpTypeImage {type_id:?} with SubpassData dimension must have Format = Unknown")]
42344244
ImageTypeSubpassDataFormatMustBeUnknown {
@@ -4627,6 +4637,30 @@ pub enum ValidationError {
46274637
/// Expected type description.
46284638
expected: &'static str,
46294639
},
4640+
/// OpImageQueryFormat/OpImageQueryOrder operand is not OpTypeImage.
4641+
#[error(
4642+
"{opcode:?} in block {block:?} of function {function:?}: expected operand to be of type OpTypeImage"
4643+
)]
4644+
ImageQueryFormatOrderNotImage {
4645+
/// The function containing the instruction.
4646+
function: Option<Id>,
4647+
/// The block containing the instruction.
4648+
block: Option<Id>,
4649+
/// The opcode.
4650+
opcode: rspirv::spirv::Op,
4651+
},
4652+
/// OpImageQueryFormat/OpImageQueryOrder cannot use TileImageDataEXT dim.
4653+
#[error(
4654+
"{opcode:?} in block {block:?} of function {function:?}: Image Dim cannot be TileImageDataEXT"
4655+
)]
4656+
ImageQueryFormatOrderTileImageDataEXT {
4657+
/// The function containing the instruction.
4658+
function: Option<Id>,
4659+
/// The block containing the instruction.
4660+
block: Option<Id>,
4661+
/// The opcode.
4662+
opcode: rspirv::spirv::Op,
4663+
},
46304664
/// OpImageQuerySizeLod used with invalid dimension.
46314665
#[error(
46324666
"OpImageQuerySizeLod in block {block:?} of function {function:?} cannot be used with dimension {dim:?}"

rust/spirv-tools-core/src/validation/rules/image.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,20 @@ impl ValidationRule for ImageTypeRule {
190190
ValidationError::ImageTypeInvalidSampledType { type_id }.into()
191191
);
192192
}
193+
194+
// Int64ImageEXT capability check: 64-bit int sampled type requires it
195+
if st_inst.class.opcode == Op::TypeInt {
196+
if let Some(Operand::LiteralBit32(width)) = st_inst.operands.first() {
197+
if *width == 64
198+
&& !ctx.has_capability(Capability::Int64ImageEXT)
199+
{
200+
return Err(
201+
ValidationError::ImageTypeRequiresInt64ImageCapability
202+
.into(),
203+
);
204+
}
205+
}
206+
}
193207
}
194208
}
195209
}
@@ -262,6 +276,18 @@ impl ValidationRule for ImageTypeRule {
262276
.into());
263277
}
264278

279+
// StorageImageMultisample: multisampled storage images require the capability
280+
// (except for TileImageDataEXT dimension)
281+
if dim != Dim::DimTileImageDataEXT
282+
&& ms != 0
283+
&& sampled == 2
284+
&& !ctx.has_capability(Capability::StorageImageMultisample)
285+
{
286+
return Err(
287+
ValidationError::ImageTypeRequiresStorageImageMultisampleCapability.into(),
288+
);
289+
}
290+
265291
// Vulkan: Sampled must be 1 or 2 (cannot be 0)
266292
if ctx.env.is_vulkan() && sampled == 0 {
267293
return Err(
@@ -1349,6 +1375,44 @@ impl ValidationRule for ImageQueryRule {
13491375
}
13501376
}
13511377

1378+
Op::ImageQueryFormat | Op::ImageQueryOrder => {
1379+
// Result must be int scalar
1380+
if let Some(result_type) = inst.result_type {
1381+
if !resolver.is_int_scalar(result_type, ctx.definitions) {
1382+
return Err(ValidationError::ImageQueryResultTypeInvalid {
1383+
function: function_id,
1384+
block: block_id,
1385+
opcode,
1386+
expected: "integer scalar",
1387+
}
1388+
.into());
1389+
}
1390+
}
1391+
1392+
// Operand must be OpTypeImage (not sampled image)
1393+
if let Some(info) = get_image_type_from_instruction(inst, ctx) {
1394+
// Dim cannot be TileImageDataEXT
1395+
if info.dim == Dim::DimTileImageDataEXT {
1396+
return Err(
1397+
ValidationError::ImageQueryFormatOrderTileImageDataEXT {
1398+
function: function_id,
1399+
block: block_id,
1400+
opcode,
1401+
}
1402+
.into(),
1403+
);
1404+
}
1405+
} else {
1406+
// Could not extract image type - operand is not an image
1407+
return Err(ValidationError::ImageQueryFormatOrderNotImage {
1408+
function: function_id,
1409+
block: block_id,
1410+
opcode,
1411+
}
1412+
.into());
1413+
}
1414+
}
1415+
13521416
_ => {}
13531417
}
13541418
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
use super::*;
2+
3+
// ============================================================================
4+
// Int64ImageEXT capability tests
5+
// ============================================================================
6+
7+
#[test]
8+
fn image_type_64bit_int_sampled_type_requires_int64image_capability() {
9+
// The grammar-level capability validation catches this before our check:
10+
// R64ui format requires Int64ImageEXT capability at the operand level.
11+
let text = [
12+
"OpCapability Shader",
13+
"OpCapability Int64",
14+
"OpCapability StorageImageExtendedFormats",
15+
"OpMemoryModel Logical GLSL450",
16+
"%u64 = OpTypeInt 64 0",
17+
"%img = OpTypeImage %u64 2D 0 0 0 2 R64ui",
18+
]
19+
.join("\n");
20+
let binary = assemble_text(&text).expect("assemble");
21+
let err = validate_module(&binary, TargetEnv::Universal1_3)
22+
.expect_err("64-bit int image without Int64ImageEXT should fail");
23+
// May fail with grammar-level MissingOperandCapability or our ImageTypeRequiresInt64ImageCapability
24+
let is_capability_error = matches!(
25+
err,
26+
ValidationError::ImageTypeRequiresInt64ImageCapability
27+
| ValidationError::MissingOperandCapability { .. }
28+
);
29+
assert!(
30+
is_capability_error,
31+
"Expected capability error, got: {err:?}"
32+
);
33+
}
34+
35+
#[test]
36+
fn image_type_64bit_int_sampled_type_passes_with_int64image_capability() {
37+
let text = [
38+
"OpCapability Shader",
39+
"OpCapability Int64",
40+
"OpCapability Int64ImageEXT",
41+
"OpCapability StorageImageExtendedFormats",
42+
"OpExtension \"SPV_EXT_shader_image_int64\"",
43+
"OpMemoryModel Logical GLSL450",
44+
"%u64 = OpTypeInt 64 0",
45+
"%img = OpTypeImage %u64 2D 0 0 0 2 R64ui",
46+
]
47+
.join("\n");
48+
let binary = assemble_text(&text).expect("assemble");
49+
validate_module(&binary, TargetEnv::Universal1_3)
50+
.expect("64-bit int image with Int64ImageEXT should pass");
51+
}
52+
53+
#[test]
54+
fn image_type_32bit_int_sampled_type_does_not_require_int64image() {
55+
let text = [
56+
"OpCapability Shader",
57+
"OpMemoryModel Logical GLSL450",
58+
"%u32 = OpTypeInt 32 0",
59+
"%img = OpTypeImage %u32 2D 0 0 0 2 R32ui",
60+
]
61+
.join("\n");
62+
let binary = assemble_text(&text).expect("assemble");
63+
validate_module(&binary, TargetEnv::Universal1_3)
64+
.expect("32-bit int image should not require Int64ImageEXT");
65+
}
66+
67+
// ============================================================================
68+
// StorageImageMultisample capability tests
69+
// ============================================================================
70+
71+
#[test]
72+
fn image_type_multisampled_storage_requires_storage_image_multisample() {
73+
let text = [
74+
"OpCapability Shader",
75+
"OpMemoryModel Logical GLSL450",
76+
"%f32 = OpTypeFloat 32",
77+
// MS=1, Sampled=2 (storage image) -> requires StorageImageMultisample
78+
"%img = OpTypeImage %f32 2D 0 0 1 2 Rgba32f",
79+
]
80+
.join("\n");
81+
let binary = assemble_text(&text).expect("assemble");
82+
let err = validate_module(&binary, TargetEnv::Universal1_3)
83+
.expect_err("multisampled storage image without StorageImageMultisample should fail");
84+
assert!(
85+
matches!(
86+
err,
87+
ValidationError::ImageTypeRequiresStorageImageMultisampleCapability
88+
),
89+
"Expected ImageTypeRequiresStorageImageMultisampleCapability, got: {err:?}"
90+
);
91+
}
92+
93+
#[test]
94+
fn image_type_multisampled_storage_passes_with_capability() {
95+
let text = [
96+
"OpCapability Shader",
97+
"OpCapability StorageImageMultisample",
98+
"OpMemoryModel Logical GLSL450",
99+
"%f32 = OpTypeFloat 32",
100+
"%img = OpTypeImage %f32 2D 0 0 1 2 Rgba32f",
101+
]
102+
.join("\n");
103+
let binary = assemble_text(&text).expect("assemble");
104+
validate_module(&binary, TargetEnv::Universal1_3)
105+
.expect("multisampled storage image with StorageImageMultisample should pass");
106+
}
107+
108+
#[test]
109+
fn image_type_multisampled_sampling_image_does_not_require_storage_multisample() {
110+
let text = [
111+
"OpCapability Shader",
112+
"OpMemoryModel Logical GLSL450",
113+
"%f32 = OpTypeFloat 32",
114+
// MS=1, Sampled=1 (sampling image, not storage) -> no capability needed
115+
"%img = OpTypeImage %f32 2D 0 0 1 1 Unknown",
116+
]
117+
.join("\n");
118+
let binary = assemble_text(&text).expect("assemble");
119+
validate_module(&binary, TargetEnv::Universal1_3)
120+
.expect("multisampled sampling image should not require StorageImageMultisample");
121+
}
122+
123+
#[test]
124+
fn image_type_non_multisampled_storage_does_not_require_storage_multisample() {
125+
let text = [
126+
"OpCapability Shader",
127+
"OpMemoryModel Logical GLSL450",
128+
"%f32 = OpTypeFloat 32",
129+
// MS=0, Sampled=2 (non-multisampled storage image) -> no capability needed
130+
"%img = OpTypeImage %f32 2D 0 0 0 2 Rgba32f",
131+
]
132+
.join("\n");
133+
let binary = assemble_text(&text).expect("assemble");
134+
validate_module(&binary, TargetEnv::Universal1_3)
135+
.expect("non-multisampled storage image should not require StorageImageMultisample");
136+
}
137+
138+
// ============================================================================
139+
// ImageQueryFormat / ImageQueryOrder tests
140+
// ============================================================================
141+
142+
#[test]
143+
fn image_query_format_valid_kernel() {
144+
// OpImageQueryFormat requires Kernel capability
145+
let text = [
146+
"OpCapability Kernel",
147+
"OpCapability Addresses",
148+
149+
"OpMemoryModel Physical64 OpenCL",
150+
"OpEntryPoint Kernel %main \"main\"",
151+
"%void = OpTypeVoid",
152+
"%int = OpTypeInt 32 0",
153+
"%float = OpTypeFloat 32",
154+
"%fn = OpTypeFunction %void",
155+
"%img_ty = OpTypeImage %float 2D 0 0 0 0 Unknown",
156+
"%ptr_img = OpTypePointer CrossWorkgroup %img_ty",
157+
"%var = OpVariable %ptr_img CrossWorkgroup",
158+
"%main = OpFunction %void None %fn",
159+
"%entry = OpLabel",
160+
"%img = OpLoad %img_ty %var",
161+
"%fmt = OpImageQueryFormat %int %img",
162+
"OpReturn",
163+
"OpFunctionEnd",
164+
]
165+
.join("\n");
166+
let binary = assemble_text(&text).expect("assemble");
167+
validate_module(&binary, TargetEnv::Universal1_3)
168+
.expect("valid OpImageQueryFormat in Kernel should pass");
169+
}
170+
171+
#[test]
172+
fn image_query_order_valid_kernel() {
173+
// OpImageQueryOrder requires Kernel capability
174+
let text = [
175+
"OpCapability Kernel",
176+
"OpCapability Addresses",
177+
178+
"OpMemoryModel Physical64 OpenCL",
179+
"OpEntryPoint Kernel %main \"main\"",
180+
"%void = OpTypeVoid",
181+
"%int = OpTypeInt 32 0",
182+
"%float = OpTypeFloat 32",
183+
"%fn = OpTypeFunction %void",
184+
"%img_ty = OpTypeImage %float 2D 0 0 0 0 Unknown",
185+
"%ptr_img = OpTypePointer CrossWorkgroup %img_ty",
186+
"%var = OpVariable %ptr_img CrossWorkgroup",
187+
"%main = OpFunction %void None %fn",
188+
"%entry = OpLabel",
189+
"%img = OpLoad %img_ty %var",
190+
"%ord = OpImageQueryOrder %int %img",
191+
"OpReturn",
192+
"OpFunctionEnd",
193+
]
194+
.join("\n");
195+
let binary = assemble_text(&text).expect("assemble");
196+
validate_module(&binary, TargetEnv::Universal1_3)
197+
.expect("valid OpImageQueryOrder in Kernel should pass");
198+
}
199+
200+
#[test]
201+
fn image_query_format_requires_int_scalar_result_kernel() {
202+
// OpImageQueryFormat result type must be int scalar
203+
let text = [
204+
"OpCapability Kernel",
205+
"OpCapability Addresses",
206+
207+
"OpMemoryModel Physical64 OpenCL",
208+
"OpEntryPoint Kernel %main \"main\"",
209+
"%void = OpTypeVoid",
210+
"%float = OpTypeFloat 32",
211+
"%fn = OpTypeFunction %void",
212+
"%img_ty = OpTypeImage %float 2D 0 0 0 0 Unknown",
213+
"%ptr_img = OpTypePointer CrossWorkgroup %img_ty",
214+
"%var = OpVariable %ptr_img CrossWorkgroup",
215+
"%main = OpFunction %void None %fn",
216+
"%entry = OpLabel",
217+
"%img = OpLoad %img_ty %var",
218+
// Using float result type instead of int - should fail
219+
"%fmt = OpImageQueryFormat %float %img",
220+
"OpReturn",
221+
"OpFunctionEnd",
222+
]
223+
.join("\n");
224+
let binary = assemble_text(&text).expect("assemble");
225+
let err = validate_module(&binary, TargetEnv::Universal1_3)
226+
.expect_err("OpImageQueryFormat with float result should fail");
227+
assert!(
228+
matches!(
229+
err,
230+
ValidationError::ImageQueryResultTypeInvalid { .. }
231+
),
232+
"Expected ImageQueryResultTypeInvalid, got: {err:?}"
233+
);
234+
}

rust/spirv-tools-core/src/validation/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod composites;
2525
mod decorations;
2626
mod entry_points;
2727
mod functions;
28+
mod image;
2829
mod layout_ordering;
2930
mod misc;
3031
mod module_basics;

0 commit comments

Comments
 (0)