spirt/spv/
lower.rs

1//! SPIR-V to SPIR-T lowering.
2
3use crate::spv::{self, spec};
4// FIXME(eddyb) import more to avoid `crate::` everywhere.
5use 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
20/// SPIR-T definition of a SPIR-V ID.
21enum 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            // FIXME(eddyb) print these with some kind of "maximum depth",
35            // instead of just describing the kind of definition.
36            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
49/// Deferred export, needed because the IDs are initially forward refs.
50enum 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
62/// Deferred [`FuncDefBody`], needed because some IDs are initially forward refs.
63struct FuncBody {
64    func_id: spv::Id,
65    func: Func,
66    insts: Vec<IntraFuncInst>,
67}
68
69struct IntraFuncInst {
70    // Instruction aspects that can be pre-lowered:
71    attrs: AttrSet,
72    result_type: Option<Type>,
73
74    without_ids: spv::Inst,
75
76    // Instruction aspects that cannot be lowered initially (due to forward refs):
77    result_id: Option<spv::Id>,
78
79    // FIXME(eddyb) change the inline size of this to fit most instructions.
80    ids: SmallVec<[spv::Id; 4]>,
81}
82
83// FIXME(eddyb) stop abusing `io::Error` for error reporting.
84fn invalid(reason: &str) -> io::Error {
85    io::Error::new(io::ErrorKind::InvalidData, format!("malformed SPIR-V ({reason})"))
86}
87
88// FIXME(eddyb) provide more information about any normalization that happened:
89// * stats about deduplication that occured through interning
90// * sets of unused global vars and functions (and types+consts only they use)
91// FIXME(eddyb) consider introducing a "deferred error" system, where `spv::lower`
92// (and more directproducers) can keep around errors in the SPIR-T IR, and still
93// have the opportunity of silencing them e.g. by removing dead code.
94impl 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        // HACK(eddyb) used to quickly check whether an `OpVariable` is global.
114        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            // Ensured above (this is the value after any endianness swapping).
120            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            // FIXME(eddyb) maybe use this somehow? (e.g. check IDs against it)
132            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            // NOTE(eddyb) not its own section, but only a "checkpoint", forcing
176            // instructions following `OpLine`/`OpNoLine` into later sections.
177            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; // HACK(eddyb) for `current_debug_line` resets.
190        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            // Handle line debuginfo early, as it doesn't have its own section,
201            // but rather can go almost anywhere among globals and functions.
202            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                // Advance to `Seq::DebugLine` if we're not there yet, forcing
230                // any following instructions to not be in earlier sections.
231                seq = seq.max(Some(Seq::DebugLine));
232                continue;
233            }
234
235            // Reset line debuginfo when crossing/leaving blocks.
236            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                // FIXME(eddyb) use `get_or_insert_default` once that's stabilized.
253                attrs.attrs.insert(Attr::SpvDebugLine {
254                    file_path: crate::OrdAssertEq(file_path),
255                    line,
256                    col,
257                });
258            }
259
260            // Take certain bitflags operands out of the instruction and rewrite
261            // them into attributes instead.
262            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            // FIXME(eddyb) move this kind of lookup into methods on some sort
275            // of "lowering context" type.
276            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                // NOTE(eddyb) debug instructions are handled earlier in the code
365                // for organizatory purposes, see `Seq` for the in-module order.
366                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                        // Absorb all following `OpSourceContinued` into `contents`.
402                        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                // NOTE(eddyb) debug instructions are handled earlier in the code
425                // for organizatory purposes, see `Seq` for the in-module order.
426                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                // NOTE(eddyb) debug instructions are handled earlier in the code
445                // for organizatory purposes, see `Seq` for the in-module order.
446                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                // NOTE(eddyb) debug instructions are handled earlier in the code
463                // for organizatory purposes, see `Seq` for the in-module order.
464                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, // FIXME(eddyb) not actually supported
478                wk.OpName,
479                wk.OpMemberName,
480                wk.OpDecorate,
481                wk.OpMemberDecorate,
482                wk.OpDecorateId, // FIXME(eddyb) not actually supported
483                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                    // Special-case `OpDecorate LinkageAttributes ... Import|Export`.
497                    [
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                // HACK(eddyb) this is not a proper implementation - one would
545                // require fixpoint (aka "μ" aka "mu") types - but for now this
546                // serves as a first approximation for a "deferred error".
547                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                    // `OpUndef` can appear either among constants, or in a
611                    // function, so at most advance `seq` to globals.
612                    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                // FIXME(eddyb) hide this from SPIR-T, it's the function return
685                // type, *not* the function type, which is in `func_type`.
686                let func_ret_type = result_type.unwrap();
687
688                let func_type_id = match (&inst.imms[..], &inst.ids[..]) {
689                    // NOTE(eddyb) the `FunctionControl` operand is already gone,
690                    // having been converted into an attribute above.
691                    (&[], &[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                    // FIXME(remove) embed IDs in errors by moving them to the
718                    // `let invalid = |...| ...;` closure that wraps insts.
719                    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        // HACK(eddyb) `OpNop` is useful for defining `DataInst`s before they're
821        // actually lowered (to be able to refer to their outputs `Value`s).
822        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        // Process function bodies, having seen the whole module.
833        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                // FIXME(eddyb) how inefficient is `FxIndexMap<spv::Id, Type>`?
856                // (vs e.g. a bitset combined with not duplicating `Type`s per-block?)
857                cfgssa_inter_block_uses: FxIndexMap<spv::Id, Type>,
858            }
859
860            // Index IDs declared within the function, first.
861            let mut local_id_defs = FxIndexMap::default();
862            // `OpPhi`s are also collected here, to assign them per-edge.
863            let mut phi_to_values = FxIndexMap::<PhiKey, SmallVec<[spv::Id; 1]>>::default();
864            // FIXME(eddyb) wouldn't `EntityOrientedDenseMap` make more sense?
865            let mut block_details = FxIndexMap::<ControlRegion, BlockDetails>::default();
866            let mut has_blocks = false;
867            let mut cfgssa_def_map = {
868                // FIXME(eddyb) in theory, this could be a toggle, but there is
869                // very little value in allowing dominance-based SSA use rules.
870                const SPIRT_CFGSSA_UNDOMINATE: bool = true;
871
872                SPIRT_CFGSSA_UNDOMINATE.then(|| {
873                    let mut def_map = crate::cfgssa::DefMap::new();
874
875                    // HACK(eddyb) allow e.g. `OpFunctionParameter` to
876                    // be treated like `OpPhi`s of the entry block.
877                    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                                // `LocalIdDef`s not needed for declarations.
901                                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                                // Error will be emitted later, below.
915                                DeclDef::Imported(_) => continue,
916                                DeclDef::Present(def) => def,
917                            };
918
919                            if opcode == wk.OpLabel {
920                                let block = if is_entry_block {
921                                    // A `ControlRegion` was defined earlier,
922                                    // to be able to create the `FuncDefBody`.
923                                    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 (&current_block, block_details) = match block_details.last_mut()
937                                {
938                                    Some(entry) => entry,
939                                    // Error will be emitted later, below.
940                                    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                                // FIXME(eddyb) use `array_chunks` when that's stable.
949                                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                                // HACK(eddyb) can't get a `DataInst` without
969                                // defining it (as a dummy) first.
970                                let inst = func_def_body.data_insts.define(
971                                    &cx,
972                                    DataInstDef {
973                                        attrs: AttrSet::default(),
974                                        // FIXME(eddyb) cache this form locally.
975                                        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((&current_block, _)) => current_block,
990                                // HACK(eddyb) ensure e.g. `OpFunctionParameter`
991                                // are treated like `OpPhi`s of the entry block.
992                                None => func_def_body.body,
993                            };
994
995                            if opcode == wk.OpLabel {
996                                // HACK(eddyb) the entry block was already added.
997                                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                        // FIXME(remove) embed IDs in errors by moving them to the
1028                        // `let invalid = |...| ...;` closure that wraps insts.
1029                        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            // HACK(eddyb) an entire separate traversal is required to find
1040            // all inter-block uses, before any blocks get lowered to SPIR-T.
1041            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                // HACK(eddyb) ensure e.g. `OpFunctionParameter`
1047                // are treated like `OpPhi`s of the entry block.
1048                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                        // FIXME(eddyb) use `array_chunks` when that's stable.
1067                        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                                // HACK(eddyb) `value_id` would be explicitly used
1075                                // in `source_block`, in a "BB args" representation,
1076                                // but phis move the use to the edge's target.
1077                                use_acc.add_use(source_block, value_id);
1078                            }
1079                        }
1080                        continue;
1081                    }
1082
1083                    // HACK(eddyb) while including merges as edges may seem useful,
1084                    // they don't participate in dominance (and thus SSA validity),
1085                    // and if there's any chance `current_block` is *not* the
1086                    // closest dominator of a merge, that merge could contain
1087                    // uses that don't belong/are illegal in `current_block`.
1088                    if [wk.OpSelectionMerge, wk.OpLoopMerge].contains(&opcode) {
1089                        continue;
1090                    }
1091
1092                    for &id in &raw_inst.ids {
1093                        // HACK(eddyb) treat all mentions of `OpLabel` IDs as
1094                        // CFG edge targets, which turns out to be accurate,
1095                        // except for `OpPhi`/`OpSelectionMerge`/`OpLoopMerge`
1096                        // (which are already special-cased above).
1097                        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                            // HACK(eddyb) this heavily relies on `add_use(_, id)`
1102                            // ignoring `id`s which aren't recognized by `def_map`.
1103                            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                // FIXME(eddyb) figure out a better name and/or organization for this.
1118                details: &'a BlockDetails,
1119
1120                // HACK(eddyb) this is probably very inefficient but allows easy
1121                // access to inter-block-used IDs, in a form directly usable in
1122                // the current block (i.e. `ControlRegion` inputs).
1123                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                // FIXME(eddyb) find a more compact name and/or make this a method.
1142                // FIXME(eddyb) this returns `LocalIdDef` even for global values.
1143                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                                // HACK(eddyb) intern `OpString`s as `Const`s on
1158                                // the fly, as it's a less likely usage than the
1159                                // `OpLine` one.
1160                                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                    // A `ControlRegion` (using an empty `Block` `ControlNode`
1216                    // as its sole child) was defined earlier,
1217                    // to be able to have an entry in `local_id_defs`.
1218                    let region = match local_id_defs[&result_id.unwrap()] {
1219                        LocalIdDef::BlockLabel(region) => region,
1220                        LocalIdDef::Value(_) => unreachable!(),
1221                    };
1222                    let details = &block_details[&region];
1223                    assert_eq!(details.label_id, result_id.unwrap());
1224                    current_block = Some(CurrentBlock {
1225                        region,
1226                        details,
1227
1228                        // HACK(eddyb) reuse `shadowed_local_id_defs` storage.
1229                        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                // HACK(eddyb) the `ControlRegion` inputs for inter-block uses
1246                // have to be inserted just after all the `OpPhi`s' region inputs,
1247                // or right away (e.g. on `OpLabel`) when there are no `OpPhi`s,
1248                // so the easiest place to insert them is before handling the
1249                // first instruction in the block that's not `OpLabel`/`OpPhi`.
1250                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                // HACK(eddyb) shadowing the closure with the same name, could
1280                // it be defined here to make use of `current_block`?
1281                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                        // Only resolve `OpPhi`s exactly once (per target).
1327                        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                    // Split the operands into value inputs (e.g. a branch's
1369                    // condition or an `OpSwitch`'s selector) and target blocks.
1370                    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                    // HACK(eddyb) we want to at least record `OpLoopMerge`s'
1451                    // impact on the shape of a loop, for restructurization.
1452                    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                    // HACK(eddyb) merges are mostly ignored - this may be lossy,
1470                    // especially wrt the `SelectionControl` and `LoopControl`
1471                    // operands, but it's not obvious how they should map to
1472                    // some "structured regions" replacement for the CFG.
1473                } 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                            // HACK(eddyb) this should be an error, but it shows
1498                            // up in Rust-GPU output (likely a zombie?).
1499                            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                                // A dummy was defined earlier, to be able to
1563                                // have an entry in `local_id_defs`.
1564                                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            // FIXME(eddyb) all functions should have the appropriate number of
1607            // `OpFunctionParameter`, even imports.
1608            if !params.is_empty() {
1609                if func_decl.params.len() != params.len() {
1610                    // FIXME(remove) embed IDs in errors by moving them to the
1611                    // `let invalid = |...| ...;` closure that wraps insts.
1612                    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                        // FIXME(remove) embed IDs in errors by moving them to the
1627                        // `let invalid = |...| ...;` closure that wraps insts.
1628                        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                // FIXME(remove) embed IDs in errors by moving them to the
1656                // `let invalid = |...| ...;` closure that wraps insts.
1657                return Err(invalid(&format!(
1658                    "in %{}, `OpPhi`s refer to non-existent edges: {}",
1659                    func_id,
1660                    edges.join(", ")
1661                )));
1662            }
1663
1664            // Sanity-check the entry block.
1665            if let Some(func_def_body) = func_def_body {
1666                if block_details[&func_def_body.body].phi_count > 0 {
1667                    // FIXME(remove) embed IDs in errors by moving them to the
1668                    // `let invalid = |...| ...;` closure that wraps insts.
1669                    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}