|
6 | 6 | use std::path::Path; |
7 | 7 |
|
8 | 8 | use rspirv::dr::Module; |
9 | | -use rspirv::spirv::{Decoration, Op}; |
| 9 | +use rspirv::spirv::{BuiltIn, Capability, Decoration, Op}; |
10 | 10 | use rustc_session::Session; |
11 | 11 | use spirv_tools::ValidationError; |
12 | 12 |
|
@@ -62,6 +62,42 @@ impl<'a> ValidationErrorContext<'a> { |
62 | 62 | } => { |
63 | 63 | self.emit_not_a_logical_pointer(*instruction, u32::from(*pointer), *source_opcode); |
64 | 64 | } |
| 65 | + ValidationError::MissingInstructionCapability { |
| 66 | + opcode, |
| 67 | + required_capability, |
| 68 | + } => { |
| 69 | + self.emit_missing_capability(*opcode, *required_capability, None); |
| 70 | + } |
| 71 | + ValidationError::MissingOperandCapability { |
| 72 | + opcode, |
| 73 | + operand_index, |
| 74 | + required_capability, |
| 75 | + } => { |
| 76 | + self.emit_missing_capability(*opcode, *required_capability, Some(*operand_index)); |
| 77 | + } |
| 78 | + ValidationError::MissingDescriptorSetDecoration { variable } => { |
| 79 | + self.emit_missing_decoration( |
| 80 | + u32::from(*variable), |
| 81 | + "DescriptorSet", |
| 82 | + "#[spirv(descriptor_set = N)]", |
| 83 | + ); |
| 84 | + } |
| 85 | + ValidationError::MissingBindingDecoration { variable } => { |
| 86 | + self.emit_missing_decoration( |
| 87 | + u32::from(*variable), |
| 88 | + "Binding", |
| 89 | + "#[spirv(binding = N)]", |
| 90 | + ); |
| 91 | + } |
| 92 | + ValidationError::InvalidBlockLayout { |
| 93 | + struct_type, |
| 94 | + reason, |
| 95 | + } => { |
| 96 | + self.emit_invalid_block_layout(u32::from(*struct_type), reason); |
| 97 | + } |
| 98 | + ValidationError::InvalidBuiltInType { builtin, expected } => { |
| 99 | + self.emit_invalid_builtin_type(*builtin, expected); |
| 100 | + } |
65 | 101 | _ => { |
66 | 102 | // Fall back to generic error message |
67 | 103 | self.emit_generic_error(error); |
@@ -144,6 +180,96 @@ impl<'a> ValidationErrorContext<'a> { |
144 | 180 | } |
145 | 181 | } |
146 | 182 |
|
| 183 | + fn emit_invalid_block_layout(&mut self, struct_type_id: u32, reason: &str) { |
| 184 | + let span = self.id_to_span(struct_type_id); |
| 185 | + let type_name = self.get_name(struct_type_id); |
| 186 | + let type_name_fallback = format!("%{}", struct_type_id); |
| 187 | + let type_name_display = type_name.as_deref().unwrap_or(&type_name_fallback); |
| 188 | + |
| 189 | + let message = format!( |
| 190 | + "struct `{}` has invalid block layout: {}", |
| 191 | + type_name_display, reason |
| 192 | + ); |
| 193 | + |
| 194 | + let mut err = if let Some(span) = span { |
| 195 | + self.sess.dcx().struct_span_err(span, message) |
| 196 | + } else { |
| 197 | + self.sess.dcx().struct_err(message) |
| 198 | + }; |
| 199 | + |
| 200 | + err.help("ensure struct members are properly aligned according to std140/std430 layout rules"); |
| 201 | + err.note(format!("module `{}`", self.filename.display())); |
| 202 | + err.emit(); |
| 203 | + } |
| 204 | + |
| 205 | + fn emit_invalid_builtin_type(&self, builtin: BuiltIn, expected: &str) { |
| 206 | + let mut err = self.sess.dcx().struct_err(format!( |
| 207 | + "BuiltIn {:?} has incorrect type, expected {}", |
| 208 | + builtin, expected |
| 209 | + )); |
| 210 | + |
| 211 | + err.help(format!( |
| 212 | + "variables with `#[spirv(builtin = \"{:?}\")]` must have the correct type", |
| 213 | + builtin |
| 214 | + )); |
| 215 | + err.note(format!("module `{}`", self.filename.display())); |
| 216 | + err.emit(); |
| 217 | + } |
| 218 | + |
| 219 | + fn emit_missing_decoration(&mut self, var_id: u32, decoration_name: &str, hint: &str) { |
| 220 | + let span = self.id_to_span(var_id); |
| 221 | + let var_name = self.get_name(var_id); |
| 222 | + let var_name_fallback = format!("%{}", var_id); |
| 223 | + let var_name_display = var_name.as_deref().unwrap_or(&var_name_fallback); |
| 224 | + |
| 225 | + let message = format!( |
| 226 | + "resource variable `{}` is missing {} decoration", |
| 227 | + var_name_display, decoration_name |
| 228 | + ); |
| 229 | + |
| 230 | + let mut err = if let Some(span) = span { |
| 231 | + self.sess.dcx().struct_span_err(span, message) |
| 232 | + } else { |
| 233 | + self.sess.dcx().struct_err(message) |
| 234 | + }; |
| 235 | + |
| 236 | + err.help(format!("add `{}` to the variable", hint)); |
| 237 | + err.note(format!("module `{}`", self.filename.display())); |
| 238 | + err.emit(); |
| 239 | + } |
| 240 | + |
| 241 | + fn emit_missing_capability( |
| 242 | + &mut self, |
| 243 | + opcode: Op, |
| 244 | + capability: Capability, |
| 245 | + operand_index: Option<usize>, |
| 246 | + ) { |
| 247 | + // Try to find an instruction with this opcode to get a span |
| 248 | + let span = self.find_instruction_span_by_opcode(opcode); |
| 249 | + |
| 250 | + let message = if let Some(idx) = operand_index { |
| 251 | + format!( |
| 252 | + "operand {} of Op{:?} requires capability {:?}", |
| 253 | + idx, opcode, capability |
| 254 | + ) |
| 255 | + } else { |
| 256 | + format!("Op{:?} requires capability {:?}", opcode, capability) |
| 257 | + }; |
| 258 | + |
| 259 | + let mut err = if let Some(span) = span { |
| 260 | + self.sess.dcx().struct_span_err(span, message) |
| 261 | + } else { |
| 262 | + self.sess.dcx().struct_err(message) |
| 263 | + }; |
| 264 | + |
| 265 | + err.help(format!( |
| 266 | + "add `#[spirv(capability({:?}))]` to your entry point function", |
| 267 | + capability |
| 268 | + )); |
| 269 | + err.note(format!("module `{}`", self.filename.display())); |
| 270 | + err.emit(); |
| 271 | + } |
| 272 | + |
147 | 273 | fn emit_not_a_logical_pointer(&mut self, instruction: Op, pointer_id: u32, source_opcode: Op) { |
148 | 274 | // Try to find a useful span - first the pointer, then the instruction using it, |
149 | 275 | // then trace back through the def chain |
@@ -190,6 +316,39 @@ impl<'a> ValidationErrorContext<'a> { |
190 | 316 | err.emit(); |
191 | 317 | } |
192 | 318 |
|
| 319 | + /// Finds the first instruction with the given opcode and returns its span. |
| 320 | + fn find_instruction_span_by_opcode(&mut self, opcode: Op) -> Option<rustc_span::Span> { |
| 321 | + let m = self.module?; |
| 322 | + |
| 323 | + // Search in functions first (most common case) |
| 324 | + for func in &m.functions { |
| 325 | + for block in &func.blocks { |
| 326 | + for inst in &block.instructions { |
| 327 | + if inst.class.opcode == opcode { |
| 328 | + if let Some(id) = inst.result_id { |
| 329 | + if let Some(span) = self.id_to_span(id) { |
| 330 | + return Some(span); |
| 331 | + } |
| 332 | + } |
| 333 | + } |
| 334 | + } |
| 335 | + } |
| 336 | + } |
| 337 | + |
| 338 | + // Search in types/globals |
| 339 | + for inst in &m.types_global_values { |
| 340 | + if inst.class.opcode == opcode { |
| 341 | + if let Some(id) = inst.result_id { |
| 342 | + if let Some(span) = self.id_to_span(id) { |
| 343 | + return Some(span); |
| 344 | + } |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + |
| 349 | + None |
| 350 | + } |
| 351 | + |
193 | 352 | /// Finds an instruction that uses the given pointer ID with the specified opcode. |
194 | 353 | fn find_instruction_using_pointer(&self, pointer_id: u32, opcode: Op) -> Option<u32> { |
195 | 354 | let m = self.module?; |
|
0 commit comments