1use crate::spv::{self, spec};
4use crate::{
6 AddrSpace, Attr, AttrSet, Const, ConstDef, ConstKind, Context, ControlNodeDef, ControlNodeKind,
7 ControlRegion, ControlRegionDef, ControlRegionInputDecl, DataInstDef, DataInstFormDef,
8 DataInstKind, DeclDef, Diag, EntityDefs, EntityList, ExportKey, Exportee, Func, FuncDecl,
9 FuncDefBody, FuncParam, FxIndexMap, GlobalVarDecl, GlobalVarDefBody, Import, InternedStr,
10 Module, SelectionKind, Type, TypeDef, TypeKind, TypeOrConst, Value, cfg, print,
11};
12use rustc_hash::FxHashMap;
13use smallvec::SmallVec;
14use std::collections::{BTreeMap, BTreeSet};
15use std::num::NonZeroU32;
16use std::path::Path;
17use std::rc::Rc;
18use std::{io, mem};
19
20enum IdDef {
22 Type(Type),
23 Const(Const),
24
25 Func(Func),
26
27 SpvExtInstImport(InternedStr),
28 SpvDebugString(InternedStr),
29}
30
31impl IdDef {
32 fn descr(&self, cx: &Context) -> String {
33 match *self {
34 IdDef::Type(_) => "a type".into(),
37 IdDef::Const(_) => "a constant".into(),
38
39 IdDef::Func(_) => "a function".into(),
40
41 IdDef::SpvExtInstImport(name) => {
42 format!("`OpExtInstImport {:?}`", &cx[name])
43 }
44 IdDef::SpvDebugString(s) => format!("`OpString {:?}`", &cx[s]),
45 }
46 }
47}
48
49enum Export {
51 Linkage {
52 name: InternedStr,
53 target_id: spv::Id,
54 },
55 EntryPoint {
56 func_id: spv::Id,
57 imms: SmallVec<[spv::Imm; 2]>,
58 interface_ids: SmallVec<[spv::Id; 4]>,
59 },
60}
61
62struct FuncBody {
64 func_id: spv::Id,
65 func: Func,
66 insts: Vec<IntraFuncInst>,
67}
68
69struct IntraFuncInst {
70 attrs: AttrSet,
72 result_type: Option<Type>,
73
74 without_ids: spv::Inst,
75
76 result_id: Option<spv::Id>,
78
79 ids: SmallVec<[spv::Id; 4]>,
81}
82
83fn invalid(reason: &str) -> io::Error {
85 io::Error::new(io::ErrorKind::InvalidData, format!("malformed SPIR-V ({reason})"))
86}
87
88impl Module {
95 pub fn lower_from_spv_file(cx: Rc<Context>, path: impl AsRef<Path>) -> io::Result<Self> {
96 Self::lower_from_spv_module_parser(cx, spv::read::ModuleParser::read_from_spv_file(path)?)
97 }
98
99 pub fn lower_from_spv_bytes(cx: Rc<Context>, spv_bytes: Vec<u8>) -> io::Result<Self> {
100 Self::lower_from_spv_module_parser(
101 cx,
102 spv::read::ModuleParser::read_from_spv_bytes(spv_bytes)?,
103 )
104 }
105
106 pub fn lower_from_spv_module_parser(
107 cx: Rc<Context>,
108 parser: spv::read::ModuleParser,
109 ) -> io::Result<Self> {
110 let spv_spec = spec::Spec::get();
111 let wk = &spv_spec.well_known;
112
113 let storage_class_function_imm = spv::Imm::Short(wk.StorageClass, wk.Function);
115
116 let mut module = {
117 let [magic, version, generator_magic, id_bound, reserved_inst_schema] = parser.header;
118
119 assert_eq!(magic, spv_spec.magic);
121
122 let [version_reserved_hi, version_major, version_minor, version_reserved_lo] =
123 version.to_be_bytes();
124
125 if (version_reserved_lo, version_reserved_hi) != (0, 0) {
126 return Err(invalid(&format!(
127 "version 0x{version:08x} is not in expected (0.major.minor.0) form"
128 )));
129 }
130
131 let _ = id_bound;
133
134 if reserved_inst_schema != 0 {
135 return Err(invalid(&format!(
136 "unknown instruction schema {reserved_inst_schema} - only 0 is supported"
137 )));
138 }
139
140 Self::new(
141 cx.clone(),
142 crate::ModuleDialect::Spv(spv::Dialect {
143 version_major,
144 version_minor,
145
146 capabilities: BTreeSet::new(),
147 extensions: BTreeSet::new(),
148
149 addressing_model: 0,
150 memory_model: 0,
151 }),
152 crate::ModuleDebugInfo::Spv(spv::ModuleDebugInfo {
153 original_generator_magic: NonZeroU32::new(generator_magic),
154
155 source_languages: BTreeMap::new(),
156 source_extensions: vec![],
157 module_processes: vec![],
158 }),
159 )
160 };
161
162 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
163 enum Seq {
164 Capability,
165 Extension,
166 ExtInstImport,
167 MemoryModel,
168 EntryPoint,
169 ExecutionMode,
170 DebugStringAndSource,
171 DebugName,
172 DebugModuleProcessed,
173 Decoration,
174
175 DebugLine,
178
179 TypeConstOrGlobalVar,
180 Function,
181 }
182 let mut seq = None;
183
184 let mut has_memory_model = false;
185 let mut pending_attrs = FxHashMap::<spv::Id, crate::AttrSetDef>::default();
186 let mut pending_imports = FxHashMap::<spv::Id, Import>::default();
187 let mut pending_exports = vec![];
188 let mut current_debug_line = None;
189 let mut current_block_id = None; let mut id_defs = FxHashMap::default();
191 let mut pending_func_bodies = vec![];
192 let mut current_func_body = None;
193
194 let mut spv_insts = parser.peekable();
195 while let Some(mut inst) = spv_insts.next().transpose()? {
196 let opcode = inst.opcode;
197
198 let invalid = |msg: &str| invalid(&format!("in {}: {}", opcode.name(), msg));
199
200 if [wk.OpLine, wk.OpNoLine].contains(&opcode) {
203 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
204
205 current_debug_line = if opcode == wk.OpLine {
206 match (&inst.imms[..], &inst.ids[..]) {
207 (
208 &[spv::Imm::Short(l_kind, line), spv::Imm::Short(c_kind, col)],
209 &[file_path_id],
210 ) => {
211 assert_eq!([l_kind, c_kind], [wk.LiteralInteger; 2]);
212 let file_path = match id_defs.get(&file_path_id) {
213 Some(&IdDef::SpvDebugString(s)) => s,
214 _ => {
215 return Err(invalid(&format!(
216 "%{file_path_id} is not an OpString"
217 )));
218 }
219 };
220 Some((file_path, line, col))
221 }
222 _ => unreachable!(),
223 }
224 } else {
225 assert!(inst.imms.is_empty() && inst.ids.is_empty());
226 None
227 };
228
229 seq = seq.max(Some(Seq::DebugLine));
232 continue;
233 }
234
235 let new_block_id = if opcode == wk.OpLabel {
237 Some(inst.result_id.unwrap())
238 } else if opcode == wk.OpFunctionEnd {
239 None
240 } else {
241 current_block_id
242 };
243 if current_block_id != new_block_id {
244 current_debug_line = None;
245 }
246 current_block_id = new_block_id;
247
248 let mut attrs =
249 inst.result_id.and_then(|id| pending_attrs.remove(&id)).unwrap_or_default();
250
251 if let Some((file_path, line, col)) = current_debug_line {
252 attrs.attrs.insert(Attr::SpvDebugLine {
254 file_path: crate::OrdAssertEq(file_path),
255 line,
256 col,
257 });
258 }
259
260 inst.imms.retain(|imm| match *imm {
263 spv::Imm::Short(kind, word) if kind == wk.FunctionControl => {
264 if word != 0 {
265 attrs.attrs.insert(Attr::SpvBitflagsOperand(*imm));
266 }
267 false
268 }
269 _ => true,
270 });
271
272 let mut attrs = cx.intern(attrs);
273
274 let result_type = inst
277 .result_type_id
278 .map(|type_id| match id_defs.get(&type_id) {
279 Some(&IdDef::Type(ty)) => Ok(ty),
280 Some(id_def) => Err(invalid(&format!(
281 "result type %{} should be a type, not a {}",
282 type_id,
283 id_def.descr(&cx)
284 ))),
285 None => Err(invalid(&format!("result type %{type_id} not defined"))),
286 })
287 .transpose()?;
288
289 let inst_category = spv_spec.instructions[opcode].category;
290
291 let next_seq = if opcode == wk.OpCapability {
292 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
293 let cap = match (&inst.imms[..], &inst.ids[..]) {
294 (&[spv::Imm::Short(kind, cap)], &[]) => {
295 assert_eq!(kind, wk.Capability);
296 cap
297 }
298 _ => unreachable!(),
299 };
300
301 match &mut module.dialect {
302 crate::ModuleDialect::Spv(dialect) => {
303 dialect.capabilities.insert(cap);
304 }
305 }
306
307 Seq::Capability
308 } else if opcode == wk.OpExtension {
309 assert!(
310 inst.result_type_id.is_none()
311 && inst.result_id.is_none()
312 && inst.ids.is_empty()
313 );
314 let ext = spv::extract_literal_string(&inst.imms)
315 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
316
317 match &mut module.dialect {
318 crate::ModuleDialect::Spv(dialect) => {
319 dialect.extensions.insert(ext);
320 }
321 }
322
323 Seq::Extension
324 } else if opcode == wk.OpExtInstImport {
325 assert!(inst.result_type_id.is_none() && inst.ids.is_empty());
326 let id = inst.result_id.unwrap();
327 let name = spv::extract_literal_string(&inst.imms)
328 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
329
330 id_defs.insert(id, IdDef::SpvExtInstImport(cx.intern(name)));
331
332 Seq::ExtInstImport
333 } else if opcode == wk.OpMemoryModel {
334 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
335 let (addressing_model, memory_model) = match (&inst.imms[..], &inst.ids[..]) {
336 (&[spv::Imm::Short(am_kind, am), spv::Imm::Short(mm_kind, mm)], &[]) => {
337 assert_eq!([am_kind, mm_kind], [wk.AddressingModel, wk.MemoryModel]);
338 (am, mm)
339 }
340 _ => unreachable!(),
341 };
342
343 if has_memory_model {
344 return Err(invalid("duplicate OpMemoryModel"));
345 }
346 has_memory_model = true;
347
348 match &mut module.dialect {
349 crate::ModuleDialect::Spv(dialect) => {
350 dialect.addressing_model = addressing_model;
351 dialect.memory_model = memory_model;
352 }
353 }
354
355 Seq::MemoryModel
356 } else if opcode == wk.OpString {
357 assert!(inst.result_type_id.is_none() && inst.ids.is_empty());
358 let id = inst.result_id.unwrap();
359 let s = spv::extract_literal_string(&inst.imms)
360 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
361
362 id_defs.insert(id, IdDef::SpvDebugString(cx.intern(s)));
363
364 Seq::DebugStringAndSource
367 } else if opcode == wk.OpSource {
368 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
369 let (lang, version) = match inst.imms[..] {
370 [spv::Imm::Short(l_kind, lang), spv::Imm::Short(v_kind, version), ..] => {
371 assert_eq!([l_kind, v_kind], [wk.SourceLanguage, wk.LiteralInteger]);
372 (lang, version)
373 }
374 _ => unreachable!(),
375 };
376
377 let debug_sources = match &mut module.debug_info {
378 crate::ModuleDebugInfo::Spv(debug_info) => debug_info
379 .source_languages
380 .entry(spv::DebugSourceLang { lang, version })
381 .or_default(),
382 };
383
384 match (&inst.imms[2..], &inst.ids[..]) {
385 (contents, &[file_path_id]) => {
386 let file_path = match id_defs.get(&file_path_id) {
387 Some(&IdDef::SpvDebugString(s)) => s,
388 _ => {
389 return Err(invalid(&format!(
390 "%{file_path_id} is not an OpString"
391 )));
392 }
393 };
394 let mut contents = if contents.is_empty() {
395 String::new()
396 } else {
397 spv::extract_literal_string(contents)
398 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?
399 };
400
401 while let Some(Ok(cont_inst)) = spv_insts.peek() {
403 if cont_inst.opcode != wk.OpSourceContinued {
404 break;
405 }
406 let cont_inst = spv_insts.next().unwrap().unwrap();
407
408 assert!(
409 cont_inst.result_type_id.is_none()
410 && cont_inst.result_id.is_none()
411 && cont_inst.ids.is_empty()
412 );
413 let cont_contents = spv::extract_literal_string(&cont_inst.imms)
414 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
415 contents += &cont_contents;
416 }
417
418 debug_sources.file_contents.insert(file_path, contents);
419 }
420 (&[], &[]) => {}
421 _ => unreachable!(),
422 }
423
424 Seq::DebugStringAndSource
427 } else if opcode == wk.OpSourceContinued {
428 return Err(invalid("must follow OpSource"));
429 } else if opcode == wk.OpSourceExtension {
430 assert!(
431 inst.result_type_id.is_none()
432 && inst.result_id.is_none()
433 && inst.ids.is_empty()
434 );
435 let ext = spv::extract_literal_string(&inst.imms)
436 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
437
438 match &mut module.debug_info {
439 crate::ModuleDebugInfo::Spv(debug_info) => {
440 debug_info.source_extensions.push(ext);
441 }
442 }
443
444 Seq::DebugStringAndSource
447 } else if opcode == wk.OpModuleProcessed {
448 assert!(
449 inst.result_type_id.is_none()
450 && inst.result_id.is_none()
451 && inst.ids.is_empty()
452 );
453 let proc = spv::extract_literal_string(&inst.imms)
454 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
455
456 match &mut module.debug_info {
457 crate::ModuleDebugInfo::Spv(debug_info) => {
458 debug_info.module_processes.push(proc);
459 }
460 }
461
462 Seq::DebugModuleProcessed
465 } else if opcode == wk.OpEntryPoint {
466 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
467
468 pending_exports.push(Export::EntryPoint {
469 func_id: inst.ids[0],
470 imms: inst.without_ids.imms,
471 interface_ids: inst.ids[1..].iter().copied().collect(),
472 });
473
474 Seq::EntryPoint
475 } else if [
476 wk.OpExecutionMode,
477 wk.OpExecutionModeId, wk.OpName,
479 wk.OpMemberName,
480 wk.OpDecorate,
481 wk.OpMemberDecorate,
482 wk.OpDecorateId, wk.OpDecorateString,
484 wk.OpMemberDecorateString,
485 ]
486 .contains(&opcode)
487 {
488 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
489
490 let target_id = inst.ids[0];
491 if inst.ids.len() > 1 {
492 return Err(invalid("unsupported decoration with ID"));
493 }
494
495 match inst.imms[..] {
496 [
498 decoration @ spv::Imm::Short(..),
499 ref name @ ..,
500 spv::Imm::Short(lt_kind, linkage_type),
501 ] if opcode == wk.OpDecorate
502 && decoration == spv::Imm::Short(wk.Decoration, wk.LinkageAttributes)
503 && lt_kind == wk.LinkageType
504 && [wk.Import, wk.Export].contains(&linkage_type) =>
505 {
506 let name = spv::extract_literal_string(name)
507 .map_err(|e| invalid(&format!("{} in {:?}", e, e.as_bytes())))?;
508 let name = cx.intern(name);
509
510 if linkage_type == wk.Import {
511 pending_imports.insert(target_id, Import::LinkName(name));
512 } else {
513 pending_exports.push(Export::Linkage { name, target_id });
514 }
515 }
516
517 _ => {
518 pending_attrs
519 .entry(target_id)
520 .or_default()
521 .attrs
522 .insert(Attr::SpvAnnotation(inst.without_ids));
523 }
524 };
525
526 if [wk.OpExecutionMode, wk.OpExecutionModeId].contains(&opcode) {
527 Seq::ExecutionMode
528 } else if [wk.OpName, wk.OpMemberName].contains(&opcode) {
529 Seq::DebugName
530 } else {
531 Seq::Decoration
532 }
533 } else if [wk.OpDecorationGroup, wk.OpGroupDecorate, wk.OpGroupMemberDecorate]
534 .contains(&opcode)
535 {
536 return Err(invalid("unsupported decoration groups (officially deprecated)"));
537 } else if opcode == wk.OpTypeForwardPointer {
538 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
539 let (id, sc) = match (&inst.imms[..], &inst.ids[..]) {
540 (&[sc], &[id]) => (id, sc),
541 _ => unreachable!(),
542 };
543
544 let ty = cx.intern(TypeDef {
548 attrs: mem::take(&mut attrs),
549 kind: TypeKind::SpvInst {
550 spv_inst: spv::Inst { opcode, imms: [sc].into_iter().collect() },
551 type_and_const_inputs: [].into_iter().collect(),
552 },
553 });
554 id_defs.insert(id, IdDef::Type(ty));
555
556 Seq::TypeConstOrGlobalVar
557 } else if inst_category == spec::InstructionCategory::Type {
558 assert!(inst.result_type_id.is_none());
559 let id = inst.result_id.unwrap();
560 let type_and_const_inputs = inst
561 .ids
562 .iter()
563 .map(|&id| match id_defs.get(&id) {
564 Some(&IdDef::Type(ty)) => Ok(TypeOrConst::Type(ty)),
565 Some(&IdDef::Const(ct)) => Ok(TypeOrConst::Const(ct)),
566 Some(id_def) => Err(id_def.descr(&cx)),
567 None => Err(format!("a forward reference to %{id}")),
568 })
569 .map(|result| {
570 result.map_err(|descr| {
571 invalid(&format!("unsupported use of {descr} in a type"))
572 })
573 })
574 .collect::<Result<_, _>>()?;
575
576 let ty = cx.intern(TypeDef {
577 attrs: mem::take(&mut attrs),
578 kind: TypeKind::SpvInst { spv_inst: inst.without_ids, type_and_const_inputs },
579 });
580 id_defs.insert(id, IdDef::Type(ty));
581
582 Seq::TypeConstOrGlobalVar
583 } else if inst_category == spec::InstructionCategory::Const || opcode == wk.OpUndef {
584 let id = inst.result_id.unwrap();
585 let const_inputs = inst
586 .ids
587 .iter()
588 .map(|&id| match id_defs.get(&id) {
589 Some(&IdDef::Const(ct)) => Ok(ct),
590 Some(id_def) => Err(id_def.descr(&cx)),
591 None => Err(format!("a forward reference to %{id}")),
592 })
593 .map(|result| {
594 result.map_err(|descr| {
595 invalid(&format!("unsupported use of {descr} in a constant"))
596 })
597 })
598 .collect::<Result<_, _>>()?;
599
600 let ct = cx.intern(ConstDef {
601 attrs: mem::take(&mut attrs),
602 ty: result_type.unwrap(),
603 kind: ConstKind::SpvInst {
604 spv_inst_and_const_inputs: Rc::new((inst.without_ids, const_inputs)),
605 },
606 });
607 id_defs.insert(id, IdDef::Const(ct));
608
609 if opcode == wk.OpUndef {
610 seq.max(Some(Seq::TypeConstOrGlobalVar)).unwrap()
613 } else {
614 Seq::TypeConstOrGlobalVar
615 }
616 } else if opcode == wk.OpVariable && current_func_body.is_none() {
617 let global_var_id = inst.result_id.unwrap();
618 let type_of_ptr_to_global_var = result_type.unwrap();
619
620 if inst.imms[0] == storage_class_function_imm {
621 return Err(invalid("`Function` storage class outside function"));
622 }
623
624 let storage_class = match inst.imms[..] {
625 [spv::Imm::Short(kind, storage_class)] => {
626 assert_eq!(kind, wk.StorageClass);
627 storage_class
628 }
629 _ => unreachable!(),
630 };
631 let initializer = match inst.ids[..] {
632 [initializer] => Some(initializer),
633 [] => None,
634 _ => unreachable!(),
635 };
636
637 let initializer = initializer
638 .map(|id| match id_defs.get(&id) {
639 Some(&IdDef::Const(ct)) => Ok(ct),
640 Some(id_def) => Err(id_def.descr(&cx)),
641 None => Err(format!("a forward reference to %{id}")),
642 })
643 .transpose()
644 .map_err(|descr| {
645 invalid(&format!(
646 "unsupported use of {descr} as the initializer of a global variable"
647 ))
648 })?;
649
650 let def = match pending_imports.remove(&global_var_id) {
651 Some(import @ Import::LinkName(name)) => {
652 if initializer.is_some() {
653 return Err(invalid(&format!(
654 "global variable with initializer decorated as `Import` of {:?}",
655 &cx[name]
656 )));
657 }
658 DeclDef::Imported(import)
659 }
660 None => DeclDef::Present(GlobalVarDefBody { initializer }),
661 };
662
663 let global_var = module.global_vars.define(&cx, GlobalVarDecl {
664 attrs: mem::take(&mut attrs),
665 type_of_ptr_to: type_of_ptr_to_global_var,
666 shape: None,
667 addr_space: AddrSpace::SpvStorageClass(storage_class),
668 def,
669 });
670 let ptr_to_global_var = cx.intern(ConstDef {
671 attrs: AttrSet::default(),
672 ty: type_of_ptr_to_global_var,
673 kind: ConstKind::PtrToGlobalVar(global_var),
674 });
675 id_defs.insert(global_var_id, IdDef::Const(ptr_to_global_var));
676
677 Seq::TypeConstOrGlobalVar
678 } else if opcode == wk.OpFunction {
679 if current_func_body.is_some() {
680 return Err(invalid("nested OpFunction while still in a function"));
681 }
682
683 let func_id = inst.result_id.unwrap();
684 let func_ret_type = result_type.unwrap();
687
688 let func_type_id = match (&inst.imms[..], &inst.ids[..]) {
689 (&[], &[func_type_id]) => func_type_id,
692 _ => unreachable!(),
693 };
694
695 let (func_type_ret_type, func_type_param_types) =
696 match id_defs.get(&func_type_id) {
697 Some(&IdDef::Type(ty)) => match &cx[ty].kind {
698 TypeKind::SpvInst { spv_inst, type_and_const_inputs }
699 if spv_inst.opcode == wk.OpTypeFunction =>
700 {
701 let mut types =
702 type_and_const_inputs.iter().map(|&ty_or_ct| match ty_or_ct {
703 TypeOrConst::Type(ty) => ty,
704 TypeOrConst::Const(_) => unreachable!(),
705 });
706 Some((types.next().unwrap(), types))
707 }
708 _ => None,
709 },
710 _ => None,
711 }
712 .ok_or_else(|| {
713 invalid(&format!("function type %{func_type_id} not an `OpTypeFunction`"))
714 })?;
715
716 if func_ret_type != func_type_ret_type {
717 return Err(invalid(
720 &print::Plan::for_root(
721 &cx,
722 &Diag::err([
723 format!("in %{}, ", func_id).into(),
724 "return type differs between `OpFunction` (".into(),
725 func_ret_type.into(),
726 ") and `OpTypeFunction` (".into(),
727 func_type_ret_type.into(),
728 ")".into(),
729 ])
730 .message,
731 )
732 .pretty_print()
733 .to_string(),
734 ));
735 }
736
737 let def = match pending_imports.remove(&func_id) {
738 Some(import) => DeclDef::Imported(import),
739 None => {
740 let mut control_regions = EntityDefs::default();
741 let body = control_regions.define(&cx, ControlRegionDef::default());
742 DeclDef::Present(FuncDefBody {
743 control_regions,
744 control_nodes: Default::default(),
745 data_insts: Default::default(),
746 body,
747 unstructured_cfg: Some(cfg::ControlFlowGraph::default()),
748 })
749 }
750 };
751
752 let func = module.funcs.define(&cx, FuncDecl {
753 attrs: mem::take(&mut attrs),
754 ret_type: func_ret_type,
755 params: func_type_param_types
756 .map(|ty| FuncParam { attrs: AttrSet::default(), ty })
757 .collect(),
758 def,
759 });
760 id_defs.insert(func_id, IdDef::Func(func));
761
762 current_func_body = Some(FuncBody { func_id, func, insts: vec![] });
763
764 Seq::Function
765 } else if opcode == wk.OpFunctionEnd {
766 assert!(inst.result_type_id.is_none() && inst.result_id.is_none());
767 assert!(inst.imms.is_empty() && inst.ids.is_empty());
768
769 let func_body = current_func_body
770 .take()
771 .ok_or_else(|| invalid("nested OpFunction while still in a function"))?;
772
773 pending_func_bodies.push(func_body);
774
775 Seq::Function
776 } else {
777 let func_body = current_func_body
778 .as_mut()
779 .ok_or_else(|| invalid("expected only inside a function"))?;
780 assert_eq!(seq, Some(Seq::Function));
781
782 func_body.insts.push(IntraFuncInst {
783 attrs: mem::take(&mut attrs),
784 result_type,
785
786 without_ids: spv::Inst { opcode, imms: inst.without_ids.imms },
787 result_id: inst.result_id,
788 ids: inst.ids,
789 });
790
791 Seq::Function
792 };
793 if let Some(prev_seq) = seq {
794 if prev_seq > next_seq {
795 return Err(invalid(&format!(
796 "out of order: {next_seq:?} instructions must precede {prev_seq:?} instructions"
797 )));
798 }
799 }
800 seq = Some(next_seq);
801
802 if attrs != Default::default() {
803 return Err(invalid("unused decorations / line debuginfo"));
804 }
805 }
806
807 if !has_memory_model {
808 return Err(invalid("missing OpMemoryModel"));
809 }
810
811 if !pending_attrs.is_empty() {
812 let ids = pending_attrs.keys().collect::<BTreeSet<_>>();
813 return Err(invalid(&format!("decorated IDs never defined: {ids:?}")));
814 }
815
816 if current_func_body.is_some() {
817 return Err(invalid("OpFunction without matching OpFunctionEnd"));
818 }
819
820 let mut cached_op_nop_form = None;
823 let mut get_op_nop_form = || {
824 *cached_op_nop_form.get_or_insert_with(|| {
825 cx.intern(DataInstFormDef {
826 kind: DataInstKind::SpvInst(wk.OpNop.into()),
827 output_type: None,
828 })
829 })
830 };
831
832 for func_body in pending_func_bodies {
834 let FuncBody { func_id, func, insts: raw_insts } = func_body;
835
836 let func_decl = &mut module.funcs[func];
837
838 #[derive(Copy, Clone)]
839 enum LocalIdDef {
840 Value(Value),
841 BlockLabel(ControlRegion),
842 }
843
844 #[derive(PartialEq, Eq, Hash)]
845 struct PhiKey {
846 source_block_id: spv::Id,
847 target_block_id: spv::Id,
848 target_phi_idx: u32,
849 }
850
851 struct BlockDetails {
852 label_id: spv::Id,
853 phi_count: usize,
854
855 cfgssa_inter_block_uses: FxIndexMap<spv::Id, Type>,
858 }
859
860 let mut local_id_defs = FxIndexMap::default();
862 let mut phi_to_values = FxIndexMap::<PhiKey, SmallVec<[spv::Id; 1]>>::default();
864 let mut block_details = FxIndexMap::<ControlRegion, BlockDetails>::default();
866 let mut has_blocks = false;
867 let mut cfgssa_def_map = {
868 const SPIRT_CFGSSA_UNDOMINATE: bool = true;
871
872 SPIRT_CFGSSA_UNDOMINATE.then(|| {
873 let mut def_map = crate::cfgssa::DefMap::new();
874
875 if let DeclDef::Present(func_def_body) = &func_decl.def {
878 def_map.add_block(func_def_body.body);
879 }
880
881 def_map
882 })
883 };
884 {
885 let mut next_param_idx = 0u32;
886 for raw_inst in &raw_insts {
887 let IntraFuncInst {
888 without_ids: spv::Inst { opcode, ref imms },
889 result_type,
890 result_id,
891 ..
892 } = *raw_inst;
893
894 if let Some(id) = result_id {
895 let local_id_def = if opcode == wk.OpFunctionParameter {
896 let idx = next_param_idx;
897 next_param_idx = idx.checked_add(1).unwrap();
898
899 let body = match &func_decl.def {
900 DeclDef::Imported(_) => continue,
902
903 DeclDef::Present(def) => def.body,
904 };
905 LocalIdDef::Value(Value::ControlRegionInput {
906 region: body,
907 input_idx: idx,
908 })
909 } else {
910 let is_entry_block = !has_blocks;
911 has_blocks = true;
912
913 let func_def_body = match &mut func_decl.def {
914 DeclDef::Imported(_) => continue,
916 DeclDef::Present(def) => def,
917 };
918
919 if opcode == wk.OpLabel {
920 let block = if is_entry_block {
921 func_def_body.body
924 } else {
925 func_def_body
926 .control_regions
927 .define(&cx, ControlRegionDef::default())
928 };
929 block_details.insert(block, BlockDetails {
930 label_id: id,
931 phi_count: 0,
932 cfgssa_inter_block_uses: Default::default(),
933 });
934 LocalIdDef::BlockLabel(block)
935 } else if opcode == wk.OpPhi {
936 let (¤t_block, block_details) = match block_details.last_mut()
937 {
938 Some(entry) => entry,
939 None => continue,
941 };
942
943 let phi_idx = block_details.phi_count;
944 block_details.phi_count = phi_idx.checked_add(1).unwrap();
945 let phi_idx = u32::try_from(phi_idx).unwrap();
946
947 assert!(imms.is_empty());
948 for value_and_source_block_id in raw_inst.ids.chunks(2) {
950 let &[value_id, source_block_id]: &[_; 2] =
951 value_and_source_block_id.try_into().unwrap();
952
953 phi_to_values
954 .entry(PhiKey {
955 source_block_id,
956 target_block_id: block_details.label_id,
957 target_phi_idx: phi_idx,
958 })
959 .or_default()
960 .push(value_id);
961 }
962
963 LocalIdDef::Value(Value::ControlRegionInput {
964 region: current_block,
965 input_idx: phi_idx,
966 })
967 } else {
968 let inst = func_def_body.data_insts.define(
971 &cx,
972 DataInstDef {
973 attrs: AttrSet::default(),
974 form: get_op_nop_form(),
976 inputs: [].into_iter().collect(),
977 }
978 .into(),
979 );
980 LocalIdDef::Value(Value::DataInstOutput(inst))
981 }
982 };
983 local_id_defs.insert(id, local_id_def);
984 }
985
986 if let Some(def_map) = &mut cfgssa_def_map {
987 if let DeclDef::Present(func_def_body) = &func_decl.def {
988 let current_block = match block_details.last() {
989 Some((¤t_block, _)) => current_block,
990 None => func_def_body.body,
993 };
994
995 if opcode == wk.OpLabel {
996 if current_block != func_def_body.body {
998 def_map.add_block(current_block);
999 }
1000 continue;
1001 }
1002
1003 if let Some(id) = result_id {
1004 def_map.add_def(current_block, id, result_type.unwrap());
1005 }
1006 }
1007 }
1008 }
1009 }
1010
1011 let mut params = SmallVec::<[_; 8]>::new();
1012
1013 let mut func_def_body = if has_blocks {
1014 match &mut func_decl.def {
1015 DeclDef::Imported(Import::LinkName(name)) => {
1016 return Err(invalid(&format!(
1017 "non-empty function %{} decorated as `Import` of {:?}",
1018 func_id, &cx[*name]
1019 )));
1020 }
1021 DeclDef::Present(def) => Some(def),
1022 }
1023 } else {
1024 match func_decl.def {
1025 DeclDef::Imported(Import::LinkName(_)) => {}
1026 DeclDef::Present(_) => {
1027 return Err(invalid(&format!(
1030 "function %{func_id} lacks any blocks, \
1031 but isn't an import either"
1032 )));
1033 }
1034 }
1035
1036 None
1037 };
1038
1039 let mut cfgssa_use_accumulator = cfgssa_def_map
1042 .as_ref()
1043 .filter(|_| func_def_body.is_some())
1044 .map(crate::cfgssa::UseAccumulator::new);
1045 if let Some(use_acc) = &mut cfgssa_use_accumulator {
1046 let mut current_block = func_def_body.as_ref().unwrap().body;
1049 for raw_inst in &raw_insts {
1050 let IntraFuncInst {
1051 without_ids: spv::Inst { opcode, ref imms },
1052 result_id,
1053 ..
1054 } = *raw_inst;
1055
1056 if opcode == wk.OpLabel {
1057 current_block = match local_id_defs[&result_id.unwrap()] {
1058 LocalIdDef::BlockLabel(control_region) => control_region,
1059 LocalIdDef::Value(_) => unreachable!(),
1060 };
1061 continue;
1062 }
1063
1064 if opcode == wk.OpPhi {
1065 assert!(imms.is_empty());
1066 for value_and_source_block_id in raw_inst.ids.chunks(2) {
1068 let &[value_id, source_block_id]: &[_; 2] =
1069 value_and_source_block_id.try_into().unwrap();
1070
1071 if let Some(&LocalIdDef::BlockLabel(source_block)) =
1072 local_id_defs.get(&source_block_id)
1073 {
1074 use_acc.add_use(source_block, value_id);
1078 }
1079 }
1080 continue;
1081 }
1082
1083 if [wk.OpSelectionMerge, wk.OpLoopMerge].contains(&opcode) {
1089 continue;
1090 }
1091
1092 for &id in &raw_inst.ids {
1093 if let Some(&LocalIdDef::BlockLabel(target_block)) = local_id_defs.get(&id)
1098 {
1099 use_acc.add_edge(current_block, target_block);
1100 } else {
1101 use_acc.add_use(current_block, id);
1104 }
1105 }
1106 }
1107 }
1108 if let Some(use_acc) = cfgssa_use_accumulator {
1109 for (block, inter_block_uses) in use_acc.into_inter_block_uses() {
1110 block_details[&block].cfgssa_inter_block_uses = inter_block_uses;
1111 }
1112 }
1113
1114 struct CurrentBlock<'a> {
1115 region: ControlRegion,
1116
1117 details: &'a BlockDetails,
1119
1120 shadowed_local_id_defs: FxIndexMap<spv::Id, LocalIdDef>,
1124 }
1125
1126 let mut current_block = None;
1127 for (raw_inst_idx, raw_inst) in raw_insts.iter().enumerate() {
1128 let lookahead_raw_inst =
1129 |dist| raw_inst_idx.checked_add(dist).and_then(|i| raw_insts.get(i));
1130
1131 let IntraFuncInst {
1132 attrs,
1133 result_type,
1134 without_ids: spv::Inst { opcode, ref imms },
1135 result_id,
1136 ref ids,
1137 } = *raw_inst;
1138
1139 let invalid = |msg: &str| invalid(&format!("in {}: {}", opcode.name(), msg));
1140
1141 let lookup_global_or_local_id_for_data_or_control_inst_input =
1144 |id| match id_defs.get(&id) {
1145 Some(&IdDef::Const(ct)) => Ok(LocalIdDef::Value(Value::Const(ct))),
1146 Some(id_def @ IdDef::Type(_)) => Err(invalid(&format!(
1147 "unsupported use of {} as an operand for \
1148 an instruction in a function",
1149 id_def.descr(&cx),
1150 ))),
1151 Some(id_def @ IdDef::Func(_)) => Err(invalid(&format!(
1152 "unsupported use of {} outside `OpFunctionCall`",
1153 id_def.descr(&cx),
1154 ))),
1155 Some(id_def @ IdDef::SpvDebugString(s)) => {
1156 if opcode == wk.OpExtInst {
1157 let ct = cx.intern(ConstDef {
1161 attrs: AttrSet::default(),
1162 ty: cx.intern(TypeKind::SpvStringLiteralForExtInst),
1163 kind: ConstKind::SpvStringLiteralForExtInst(*s),
1164 });
1165 Ok(LocalIdDef::Value(Value::Const(ct)))
1166 } else {
1167 Err(invalid(&format!(
1168 "unsupported use of {} outside `OpSource`, \
1169 `OpLine`, or `OpExtInst`",
1170 id_def.descr(&cx),
1171 )))
1172 }
1173 }
1174 Some(id_def @ IdDef::SpvExtInstImport(_)) => Err(invalid(&format!(
1175 "unsupported use of {} outside `OpExtInst`",
1176 id_def.descr(&cx),
1177 ))),
1178 None => local_id_defs
1179 .get(&id)
1180 .copied()
1181 .ok_or_else(|| invalid(&format!("undefined ID %{id}",))),
1182 };
1183
1184 if opcode == wk.OpFunctionParameter {
1185 if current_block.is_some() {
1186 return Err(invalid(
1187 "out of order: `OpFunctionParameter`s should come \
1188 before the function's blocks",
1189 ));
1190 }
1191
1192 assert!(imms.is_empty() && ids.is_empty());
1193
1194 let ty = result_type.unwrap();
1195 params.push(FuncParam { attrs, ty });
1196 if let Some(func_def_body) = &mut func_def_body {
1197 func_def_body
1198 .at_mut_body()
1199 .def()
1200 .inputs
1201 .push(ControlRegionInputDecl { attrs, ty });
1202 }
1203 continue;
1204 }
1205 let func_def_body = func_def_body.as_deref_mut().unwrap();
1206
1207 let is_last_in_block = lookahead_raw_inst(1)
1208 .map_or(true, |next_raw_inst| next_raw_inst.without_ids.opcode == wk.OpLabel);
1209
1210 if opcode == wk.OpLabel {
1211 if is_last_in_block {
1212 return Err(invalid("block lacks terminator instruction"));
1213 }
1214
1215 let region = match local_id_defs[&result_id.unwrap()] {
1219 LocalIdDef::BlockLabel(region) => region,
1220 LocalIdDef::Value(_) => unreachable!(),
1221 };
1222 let details = &block_details[®ion];
1223 assert_eq!(details.label_id, result_id.unwrap());
1224 current_block = Some(CurrentBlock {
1225 region,
1226 details,
1227
1228 shadowed_local_id_defs: current_block
1230 .take()
1231 .map(|CurrentBlock { mut shadowed_local_id_defs, .. }| {
1232 shadowed_local_id_defs.clear();
1233 shadowed_local_id_defs
1234 })
1235 .unwrap_or_default(),
1236 });
1237 continue;
1238 }
1239 let current_block = current_block.as_mut().ok_or_else(|| {
1240 invalid("out of order: not expected before the function's blocks")
1241 })?;
1242 let current_block_control_region_def =
1243 &mut func_def_body.control_regions[current_block.region];
1244
1245 if opcode != wk.OpPhi
1251 && current_block.shadowed_local_id_defs.is_empty()
1252 && !current_block.details.cfgssa_inter_block_uses.is_empty()
1253 {
1254 assert!(current_block_control_region_def.children.is_empty());
1255
1256 current_block.shadowed_local_id_defs.extend(
1257 current_block.details.cfgssa_inter_block_uses.iter().map(
1258 |(&used_id, &ty)| {
1259 let input_idx = current_block_control_region_def
1260 .inputs
1261 .len()
1262 .try_into()
1263 .unwrap();
1264 current_block_control_region_def
1265 .inputs
1266 .push(ControlRegionInputDecl { attrs: AttrSet::default(), ty });
1267 (
1268 used_id,
1269 LocalIdDef::Value(Value::ControlRegionInput {
1270 region: current_block.region,
1271 input_idx,
1272 }),
1273 )
1274 },
1275 ),
1276 );
1277 }
1278
1279 let lookup_global_or_local_id_for_data_or_control_inst_input =
1282 |id| match current_block.shadowed_local_id_defs.get(&id) {
1283 Some(&shadowed) => Ok(shadowed),
1284 None => lookup_global_or_local_id_for_data_or_control_inst_input(id),
1285 };
1286
1287 if is_last_in_block {
1288 if opcode.def().category != spec::InstructionCategory::ControlFlow
1289 || [wk.OpPhi, wk.OpSelectionMerge, wk.OpLoopMerge].contains(&opcode)
1290 {
1291 return Err(invalid(
1292 "non-control-flow instruction cannot be used \
1293 as the terminator instruction of a block",
1294 ));
1295 }
1296
1297 let mut target_inputs = FxIndexMap::default();
1298 let descr_phi_case = |phi_key: &PhiKey| {
1299 format!(
1300 "`OpPhi` (#{} in %{})'s case for source block %{}",
1301 phi_key.target_phi_idx,
1302 phi_key.target_block_id,
1303 phi_key.source_block_id,
1304 )
1305 };
1306 let phi_value_id_to_value = |phi_key: &PhiKey, id| {
1307 match lookup_global_or_local_id_for_data_or_control_inst_input(id)? {
1308 LocalIdDef::Value(v) => Ok(v),
1309 LocalIdDef::BlockLabel { .. } => Err(invalid(&format!(
1310 "unsupported use of block label as the value for {}",
1311 descr_phi_case(phi_key)
1312 ))),
1313 }
1314 };
1315 let mut record_cfg_edge = |target_block| -> io::Result<()> {
1316 use indexmap::map::Entry;
1317
1318 let target_block_details = &block_details[&target_block];
1319
1320 if target_block_details.phi_count == 0
1321 && target_block_details.cfgssa_inter_block_uses.is_empty()
1322 {
1323 return Ok(());
1324 }
1325
1326 let target_inputs_entry = match target_inputs.entry(target_block) {
1328 Entry::Occupied(_) => return Ok(()),
1329 Entry::Vacant(entry) => entry,
1330 };
1331
1332 let inputs = (0..target_block_details.phi_count).map(|target_phi_idx| {
1333 let phi_key = PhiKey {
1334 source_block_id: current_block.details.label_id,
1335 target_block_id: target_block_details.label_id,
1336 target_phi_idx: target_phi_idx.try_into().unwrap(),
1337 };
1338 let phi_value_ids =
1339 phi_to_values.swap_remove(&phi_key).unwrap_or_default();
1340
1341 match phi_value_ids[..] {
1342 [] => Err(invalid(&format!(
1343 "{} is missing",
1344 descr_phi_case(&phi_key)
1345 ))),
1346 [id] => phi_value_id_to_value(&phi_key, id),
1347 [..] => Err(invalid(&format!(
1348 "{} is duplicated",
1349 descr_phi_case(&phi_key)
1350 ))),
1351 }
1352 });
1353 let inputs = inputs.chain(
1354 target_block_details.cfgssa_inter_block_uses.keys().map(|&used_id| {
1355 match lookup_global_or_local_id_for_data_or_control_inst_input(
1356 used_id,
1357 )? {
1358 LocalIdDef::Value(v) => Ok(v),
1359 LocalIdDef::BlockLabel(_) => unreachable!(),
1360 }
1361 }),
1362 );
1363 target_inputs_entry.insert(inputs.collect::<Result<_, _>>()?);
1364
1365 Ok(())
1366 };
1367
1368 let mut inputs = SmallVec::new();
1371 let mut targets = SmallVec::new();
1372 for &id in ids {
1373 match lookup_global_or_local_id_for_data_or_control_inst_input(id)? {
1374 LocalIdDef::Value(v) => {
1375 if !targets.is_empty() {
1376 return Err(invalid(
1377 "out of order: value operand \
1378 after target label ID",
1379 ));
1380 }
1381 inputs.push(v);
1382 }
1383 LocalIdDef::BlockLabel(target) => {
1384 record_cfg_edge(target)?;
1385 targets.push(target);
1386 }
1387 }
1388 }
1389
1390 let kind = if opcode == wk.OpUnreachable {
1391 assert!(targets.is_empty() && inputs.is_empty());
1392 cfg::ControlInstKind::Unreachable
1393 } else if [wk.OpReturn, wk.OpReturnValue].contains(&opcode) {
1394 assert!(targets.is_empty() && inputs.len() <= 1);
1395 cfg::ControlInstKind::Return
1396 } else if targets.is_empty() {
1397 cfg::ControlInstKind::ExitInvocation(cfg::ExitInvocationKind::SpvInst(
1398 raw_inst.without_ids.clone(),
1399 ))
1400 } else if opcode == wk.OpBranch {
1401 assert_eq!((targets.len(), inputs.len()), (1, 0));
1402 cfg::ControlInstKind::Branch
1403 } else if opcode == wk.OpBranchConditional {
1404 assert_eq!((targets.len(), inputs.len()), (2, 1));
1405 cfg::ControlInstKind::SelectBranch(SelectionKind::BoolCond)
1406 } else if opcode == wk.OpSwitch {
1407 cfg::ControlInstKind::SelectBranch(SelectionKind::SpvInst(
1408 raw_inst.without_ids.clone(),
1409 ))
1410 } else {
1411 return Err(invalid("unsupported control-flow instruction"));
1412 };
1413
1414 func_def_body
1415 .unstructured_cfg
1416 .as_mut()
1417 .unwrap()
1418 .control_inst_on_exit_from
1419 .insert(current_block.region, cfg::ControlInst {
1420 attrs,
1421 kind,
1422 inputs,
1423 targets,
1424 target_inputs,
1425 });
1426 } else if opcode == wk.OpPhi {
1427 if !current_block_control_region_def.children.is_empty() {
1428 return Err(invalid(
1429 "out of order: `OpPhi`s should come before \
1430 the rest of the block's instructions",
1431 ));
1432 }
1433
1434 current_block_control_region_def
1435 .inputs
1436 .push(ControlRegionInputDecl { attrs, ty: result_type.unwrap() });
1437 } else if [wk.OpSelectionMerge, wk.OpLoopMerge].contains(&opcode) {
1438 let is_second_to_last_in_block = lookahead_raw_inst(2)
1439 .map_or(true, |next_raw_inst| {
1440 next_raw_inst.without_ids.opcode == wk.OpLabel
1441 });
1442
1443 if !is_second_to_last_in_block {
1444 return Err(invalid(
1445 "out of order: a merge instruction should be the last \
1446 instruction before the block's terminator",
1447 ));
1448 }
1449
1450 if opcode == wk.OpLoopMerge {
1453 assert_eq!(ids.len(), 2);
1454 let loop_merge_target =
1455 match lookup_global_or_local_id_for_data_or_control_inst_input(ids[0])?
1456 {
1457 LocalIdDef::Value(_) => return Err(invalid("expected label ID")),
1458 LocalIdDef::BlockLabel(target) => target,
1459 };
1460
1461 func_def_body
1462 .unstructured_cfg
1463 .as_mut()
1464 .unwrap()
1465 .loop_merge_to_loop_header
1466 .insert(loop_merge_target, current_block.region);
1467 }
1468
1469 } else {
1474 let mut ids = &ids[..];
1475 let kind = if opcode == wk.OpFunctionCall {
1476 assert!(imms.is_empty());
1477 let callee_id = ids[0];
1478 let maybe_callee = id_defs
1479 .get(&callee_id)
1480 .map(|id_def| match *id_def {
1481 IdDef::Func(func) => Ok(func),
1482 _ => Err(id_def.descr(&cx)),
1483 })
1484 .transpose()
1485 .map_err(|descr| {
1486 invalid(&format!(
1487 "unsupported use of {descr} as the `OpFunctionCall` callee"
1488 ))
1489 })?;
1490
1491 match maybe_callee {
1492 Some(callee) => {
1493 ids = &ids[1..];
1494 DataInstKind::FuncCall(callee)
1495 }
1496
1497 None => DataInstKind::SpvInst(raw_inst.without_ids.clone()),
1500 }
1501 } else if opcode == wk.OpExtInst {
1502 let ext_set_id = ids[0];
1503 ids = &ids[1..];
1504
1505 let inst = match imms[..] {
1506 [spv::Imm::Short(kind, inst)] => {
1507 assert_eq!(kind, wk.LiteralExtInstInteger);
1508 inst
1509 }
1510 _ => unreachable!(),
1511 };
1512
1513 let ext_set = match id_defs.get(&ext_set_id) {
1514 Some(&IdDef::SpvExtInstImport(name)) => Ok(name),
1515 Some(id_def) => Err(id_def.descr(&cx)),
1516 None => Err(format!("unknown ID %{ext_set_id}")),
1517 }
1518 .map_err(|descr| {
1519 invalid(&format!(
1520 "unsupported use of {descr} as the `OpExtInst` \
1521 extended instruction set ID"
1522 ))
1523 })?;
1524
1525 DataInstKind::SpvExtInst { ext_set, inst }
1526 } else {
1527 DataInstKind::SpvInst(raw_inst.without_ids.clone())
1528 };
1529
1530 let data_inst_def = DataInstDef {
1531 attrs,
1532 form: cx.intern(DataInstFormDef {
1533 kind,
1534 output_type: result_id
1535 .map(|_| {
1536 result_type.ok_or_else(|| {
1537 invalid(
1538 "expected value-producing instruction, \
1539 with a result type",
1540 )
1541 })
1542 })
1543 .transpose()?,
1544 }),
1545 inputs: ids
1546 .iter()
1547 .map(|&id| {
1548 match lookup_global_or_local_id_for_data_or_control_inst_input(id)?
1549 {
1550 LocalIdDef::Value(v) => Ok(v),
1551 LocalIdDef::BlockLabel { .. } => Err(invalid(
1552 "unsupported use of block label as a value, \
1553 in non-terminator instruction",
1554 )),
1555 }
1556 })
1557 .collect::<io::Result<_>>()?,
1558 };
1559 let inst = match result_id {
1560 Some(id) => match local_id_defs[&id] {
1561 LocalIdDef::Value(Value::DataInstOutput(inst)) => {
1562 func_def_body.data_insts[inst] = data_inst_def.into();
1565
1566 inst
1567 }
1568 _ => unreachable!(),
1569 },
1570 None => func_def_body.data_insts.define(&cx, data_inst_def.into()),
1571 };
1572
1573 let current_block_control_node = current_block_control_region_def
1574 .children
1575 .iter()
1576 .last
1577 .filter(|&last_node| {
1578 matches!(
1579 func_def_body.control_nodes[last_node].kind,
1580 ControlNodeKind::Block { .. }
1581 )
1582 })
1583 .unwrap_or_else(|| {
1584 let block_node = func_def_body.control_nodes.define(
1585 &cx,
1586 ControlNodeDef {
1587 kind: ControlNodeKind::Block { insts: EntityList::empty() },
1588 outputs: SmallVec::new(),
1589 }
1590 .into(),
1591 );
1592 current_block_control_region_def
1593 .children
1594 .insert_last(block_node, &mut func_def_body.control_nodes);
1595 block_node
1596 });
1597 match &mut func_def_body.control_nodes[current_block_control_node].kind {
1598 ControlNodeKind::Block { insts } => {
1599 insts.insert_last(inst, &mut func_def_body.data_insts);
1600 }
1601 _ => unreachable!(),
1602 }
1603 }
1604 }
1605
1606 if !params.is_empty() {
1609 if func_decl.params.len() != params.len() {
1610 return Err(invalid(&format!(
1613 "in %{}, param count differs between `OpTypeFunction` ({}) \
1614 and `OpFunctionParameter`s ({})",
1615 func_id,
1616 func_decl.params.len(),
1617 params.len(),
1618 )));
1619 }
1620
1621 for (i, (func_decl_param, param)) in
1622 func_decl.params.iter_mut().zip(params).enumerate()
1623 {
1624 func_decl_param.attrs = param.attrs;
1625 if func_decl_param.ty != param.ty {
1626 return Err(invalid(
1629 &print::Plan::for_root(
1630 &cx,
1631 &Diag::err([
1632 format!("in %{}, ", func_id).into(),
1633 format!("param #{i}'s type differs between `OpTypeFunction` (")
1634 .into(),
1635 func_decl_param.ty.into(),
1636 ") and `OpFunctionParameter` (".into(),
1637 param.ty.into(),
1638 ")".into(),
1639 ])
1640 .message,
1641 )
1642 .pretty_print()
1643 .to_string(),
1644 ));
1645 }
1646 }
1647 }
1648
1649 if !phi_to_values.is_empty() {
1650 let mut edges = phi_to_values
1651 .keys()
1652 .map(|key| format!("%{} -> %{}", key.source_block_id, key.target_block_id))
1653 .collect::<Vec<_>>();
1654 edges.dedup();
1655 return Err(invalid(&format!(
1658 "in %{}, `OpPhi`s refer to non-existent edges: {}",
1659 func_id,
1660 edges.join(", ")
1661 )));
1662 }
1663
1664 if let Some(func_def_body) = func_def_body {
1666 if block_details[&func_def_body.body].phi_count > 0 {
1667 return Err(invalid(&format!(
1670 "in %{func_id}, the entry block contains `OpPhi`s"
1671 )));
1672 }
1673 }
1674 }
1675
1676 assert!(module.exports.is_empty());
1677 module.exports = pending_exports
1678 .into_iter()
1679 .map(|export| match export {
1680 Export::Linkage { name, target_id } => {
1681 let exportee = match id_defs.get(&target_id) {
1682 Some(id_def @ &IdDef::Const(ct)) => match cx[ct].kind {
1683 ConstKind::PtrToGlobalVar(gv) => Ok(Exportee::GlobalVar(gv)),
1684 _ => Err(id_def.descr(&cx)),
1685 },
1686 Some(&IdDef::Func(func)) => Ok(Exportee::Func(func)),
1687 Some(id_def) => Err(id_def.descr(&cx)),
1688 None => Err(format!("unknown ID %{target_id}")),
1689 }
1690 .map_err(|descr| {
1691 invalid(&format!(
1692 "unsupported use of {descr} as the `LinkageAttributes` target"
1693 ))
1694 })?;
1695
1696 Ok((ExportKey::LinkName(name), exportee))
1697 }
1698
1699 Export::EntryPoint {
1700 func_id,
1701 imms,
1702 interface_ids,
1703 } => {
1704 let func = match id_defs.get(&func_id) {
1705 Some(&IdDef::Func(func)) => Ok(func),
1706 Some(id_def) => Err(id_def.descr(&cx)),
1707 None => Err(format!("unknown ID %{func_id}")),
1708 }
1709 .map_err(|descr| {
1710 invalid(&format!(
1711 "unsupported use of {descr} as the `OpEntryPoint` target"
1712 ))
1713 })?;
1714 let interface_global_vars = interface_ids
1715 .into_iter()
1716 .map(|id| match id_defs.get(&id) {
1717 Some(id_def @ &IdDef::Const(ct)) => match cx[ct].kind {
1718 ConstKind::PtrToGlobalVar(gv) => Ok(gv),
1719 _ => Err(id_def.descr(&cx)),
1720 },
1721 Some(id_def) => Err(id_def.descr(&cx)),
1722 None => Err(format!("unknown ID %{id}")),
1723 })
1724 .map(|result| {
1725 result.map_err(|descr| {
1726 invalid(&format!(
1727 "unsupported use of {descr} as an `OpEntryPoint` interface variable"
1728 ))
1729 })
1730 })
1731 .collect::<Result<_, _>>()?;
1732 Ok((
1733 ExportKey::SpvEntryPoint {
1734 imms,
1735 interface_global_vars,
1736 },
1737 Exportee::Func(func),
1738 ))
1739 }
1740 })
1741 .collect::<io::Result<_>>()?;
1742
1743 Ok(module)
1744 }
1745}