Skip to main content

oxilean_codegen/spirv_backend/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::HashMap;
6
7use std::collections::{HashSet, VecDeque};
8
9/// Image format for OpTypeImage.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum ImageFormat {
12    Unknown,
13    Rgba32f,
14    Rgba16f,
15    R32f,
16    Rgba8,
17    R32i,
18    R32ui,
19}
20/// SPIR-V decoration values.
21#[derive(Debug, Clone, PartialEq)]
22pub enum Decoration {
23    RelaxedPrecision,
24    SpecId(u32),
25    Block,
26    BufferBlock,
27    RowMajor,
28    ColMajor,
29    ArrayStride(u32),
30    MatrixStride(u32),
31    GlslShared,
32    GlslPacked,
33    CPacked,
34    BuiltIn(BuiltIn),
35    NoPerspective,
36    Flat,
37    Patch,
38    Centroid,
39    Sample,
40    Invariant,
41    Restrict,
42    Aliased,
43    Volatile,
44    Constant,
45    Coherent,
46    NonWritable,
47    NonReadable,
48    Uniform,
49    UniformId(u32),
50    SaturatedConversion,
51    Stream(u32),
52    Location(u32),
53    Component(u32),
54    Index(u32),
55    Binding(u32),
56    DescriptorSet(u32),
57    Offset(u32),
58    XfbBuffer(u32),
59    XfbStride(u32),
60    FuncParamAttr(u32),
61    FPRoundingMode(u32),
62    FPFastMathMode(u32),
63    LinkageAttributes(String, u32),
64    NoContraction,
65    InputAttachmentIndex(u32),
66    Alignment(u32),
67    MaxByteOffset(u64),
68    AlignmentId(u32),
69    MaxByteOffsetId(u32),
70}
71/// SPIR-V addressing models.
72#[derive(Debug, Clone, PartialEq)]
73pub enum AddressingModel {
74    Logical,
75    Physical32,
76    Physical64,
77    PhysicalStorageBuffer64,
78}
79#[allow(dead_code)]
80#[derive(Debug, Clone)]
81pub struct SPIRVPassConfig {
82    pub phase: SPIRVPassPhase,
83    pub enabled: bool,
84    pub max_iterations: u32,
85    pub debug_output: bool,
86    pub pass_name: String,
87}
88impl SPIRVPassConfig {
89    #[allow(dead_code)]
90    pub fn new(name: impl Into<String>, phase: SPIRVPassPhase) -> Self {
91        SPIRVPassConfig {
92            phase,
93            enabled: true,
94            max_iterations: 10,
95            debug_output: false,
96            pass_name: name.into(),
97        }
98    }
99    #[allow(dead_code)]
100    pub fn disabled(mut self) -> Self {
101        self.enabled = false;
102        self
103    }
104    #[allow(dead_code)]
105    pub fn with_debug(mut self) -> Self {
106        self.debug_output = true;
107        self
108    }
109    #[allow(dead_code)]
110    pub fn max_iter(mut self, n: u32) -> Self {
111        self.max_iterations = n;
112        self
113    }
114}
115/// A single SPIR-V instruction.
116///
117/// Every instruction has an opcode, optional result ID, and a list of
118/// word operands. The result type ID is stored as the first operand
119/// when the instruction produces a value.
120#[derive(Debug, Clone, PartialEq)]
121pub struct SpirVInstruction {
122    /// Result ID assigned to this instruction (None for side-effecting ops)
123    pub result_id: Option<u32>,
124    /// Result type ID (present iff result_id is Some)
125    pub result_type_id: Option<u32>,
126    /// The SPIR-V opcode
127    pub opcode: SpirVOp,
128    /// Additional word operands (IDs, literals, enumerants)
129    pub operands: Vec<u32>,
130}
131impl SpirVInstruction {
132    /// Create an instruction that produces a result value.
133    pub fn with_result(
134        result_id: u32,
135        result_type_id: u32,
136        opcode: SpirVOp,
137        operands: Vec<u32>,
138    ) -> Self {
139        Self {
140            result_id: Some(result_id),
141            result_type_id: Some(result_type_id),
142            opcode,
143            operands,
144        }
145    }
146    /// Create an instruction without a result (e.g., OpStore, OpReturn).
147    pub fn no_result(opcode: SpirVOp, operands: Vec<u32>) -> Self {
148        Self {
149            result_id: None,
150            result_type_id: None,
151            opcode,
152            operands,
153        }
154    }
155    /// Emit the instruction as a SPIR-V assembly text line.
156    pub fn emit_text(&self) -> String {
157        let result_part = match (self.result_id, self.result_type_id) {
158            (Some(rid), Some(rtid)) => format!("%{} = %{} ", rid, rtid),
159            _ => String::new(),
160        };
161        let ops: Vec<String> = self.operands.iter().map(|o| format!("%{}", o)).collect();
162        if ops.is_empty() {
163            format!("{}{}", result_part, self.opcode)
164        } else {
165            format!("{}{} {}", result_part, self.opcode, ops.join(" "))
166        }
167    }
168    /// Estimate the word count of this instruction.
169    pub fn word_count(&self) -> u32 {
170        let base = 1;
171        let result_words = if self.result_id.is_some() { 2 } else { 0 };
172        base + result_words + self.operands.len() as u32
173    }
174}
175#[allow(dead_code)]
176pub struct SPIRVPassRegistry {
177    pub(super) configs: Vec<SPIRVPassConfig>,
178    pub(super) stats: std::collections::HashMap<String, SPIRVPassStats>,
179}
180impl SPIRVPassRegistry {
181    #[allow(dead_code)]
182    pub fn new() -> Self {
183        SPIRVPassRegistry {
184            configs: Vec::new(),
185            stats: std::collections::HashMap::new(),
186        }
187    }
188    #[allow(dead_code)]
189    pub fn register(&mut self, config: SPIRVPassConfig) {
190        self.stats
191            .insert(config.pass_name.clone(), SPIRVPassStats::new());
192        self.configs.push(config);
193    }
194    #[allow(dead_code)]
195    pub fn enabled_passes(&self) -> Vec<&SPIRVPassConfig> {
196        self.configs.iter().filter(|c| c.enabled).collect()
197    }
198    #[allow(dead_code)]
199    pub fn get_stats(&self, name: &str) -> Option<&SPIRVPassStats> {
200        self.stats.get(name)
201    }
202    #[allow(dead_code)]
203    pub fn total_passes(&self) -> usize {
204        self.configs.len()
205    }
206    #[allow(dead_code)]
207    pub fn enabled_count(&self) -> usize {
208        self.enabled_passes().len()
209    }
210    #[allow(dead_code)]
211    pub fn update_stats(&mut self, name: &str, changes: u64, time_ms: u64, iter: u32) {
212        if let Some(stats) = self.stats.get_mut(name) {
213            stats.record_run(changes, time_ms, iter);
214        }
215    }
216}
217/// SPIR-V execution models (shader stage).
218#[derive(Debug, Clone, PartialEq)]
219pub enum ExecutionModel {
220    Vertex,
221    TessellationControl,
222    TessellationEvaluation,
223    Geometry,
224    Fragment,
225    GLCompute,
226    Kernel,
227    TaskNV,
228    MeshNV,
229    RayGenerationKHR,
230    IntersectionKHR,
231    AnyHitKHR,
232    ClosestHitKHR,
233    MissKHR,
234    CallableKHR,
235}
236#[allow(dead_code)]
237#[derive(Debug, Clone)]
238pub struct SPIRVCacheEntry {
239    pub key: String,
240    pub data: Vec<u8>,
241    pub timestamp: u64,
242    pub valid: bool,
243}
244/// A basic block within a SPIR-V function.
245#[derive(Debug, Clone)]
246pub struct SpirVBasicBlock {
247    /// Label ID for this block (OpLabel)
248    pub label_id: u32,
249    /// Instructions within this block (excluding the label)
250    pub instructions: Vec<SpirVInstruction>,
251}
252impl SpirVBasicBlock {
253    /// Create a new empty basic block.
254    pub fn new(label_id: u32) -> Self {
255        Self {
256            label_id,
257            instructions: Vec::new(),
258        }
259    }
260    /// Add an instruction.
261    pub fn push(&mut self, instr: SpirVInstruction) {
262        self.instructions.push(instr);
263    }
264    /// Emit the basic block as SPIR-V assembly text.
265    pub fn emit_text(&self) -> String {
266        let mut lines = Vec::new();
267        lines.push(format!("%{} = OpLabel", self.label_id));
268        for instr in &self.instructions {
269            lines.push(format!("  {}", instr.emit_text()));
270        }
271        lines.join("\n")
272    }
273    /// Count the instructions in this block.
274    pub fn instr_count(&self) -> usize {
275        self.instructions.len()
276    }
277}
278/// SPIR-V capabilities (OpCapability).
279#[derive(Debug, Clone, PartialEq, Eq, Hash)]
280pub enum SpirVCapability {
281    Matrix,
282    Shader,
283    Geometry,
284    Tessellation,
285    Addresses,
286    Linkage,
287    Kernel,
288    Vector16,
289    Float16Buffer,
290    Float16,
291    Float64,
292    Int64,
293    Int64Atomics,
294    ImageBasic,
295    ImageReadWrite,
296    ImageMipmap,
297    Sampled1D,
298    Image1D,
299    SampledCubeArray,
300    SampledBuffer,
301    ImageBuffer,
302    ImageMSArray,
303    StorageImageExtendedFormats,
304    ImageQuery,
305    DerivativeControl,
306    InterpolationFunction,
307    TransformFeedback,
308    GeometryStreams,
309    StorageImageReadWithoutFormat,
310    StorageImageWriteWithoutFormat,
311    MultiViewport,
312    SubgroupDispatch,
313    NamedBarrier,
314    PipeStorage,
315    GroupNonUniform,
316    GroupNonUniformVote,
317    GroupNonUniformArithmetic,
318    GroupNonUniformBallot,
319    GroupNonUniformShuffle,
320    GroupNonUniformShuffleRelative,
321    VulkanMemoryModel,
322    PhysicalStorageBufferAddresses,
323    DemoteToHelperInvocation,
324    AtomicFloat32AddExt,
325    AtomicFloat64AddExt,
326}
327#[allow(dead_code)]
328#[derive(Debug, Clone)]
329pub struct SPIRVAnalysisCache {
330    pub(super) entries: std::collections::HashMap<String, SPIRVCacheEntry>,
331    pub(super) max_size: usize,
332    pub(super) hits: u64,
333    pub(super) misses: u64,
334}
335impl SPIRVAnalysisCache {
336    #[allow(dead_code)]
337    pub fn new(max_size: usize) -> Self {
338        SPIRVAnalysisCache {
339            entries: std::collections::HashMap::new(),
340            max_size,
341            hits: 0,
342            misses: 0,
343        }
344    }
345    #[allow(dead_code)]
346    pub fn get(&mut self, key: &str) -> Option<&SPIRVCacheEntry> {
347        if self.entries.contains_key(key) {
348            self.hits += 1;
349            self.entries.get(key)
350        } else {
351            self.misses += 1;
352            None
353        }
354    }
355    #[allow(dead_code)]
356    pub fn insert(&mut self, key: String, data: Vec<u8>) {
357        if self.entries.len() >= self.max_size {
358            if let Some(oldest) = self.entries.keys().next().cloned() {
359                self.entries.remove(&oldest);
360            }
361        }
362        self.entries.insert(
363            key.clone(),
364            SPIRVCacheEntry {
365                key,
366                data,
367                timestamp: 0,
368                valid: true,
369            },
370        );
371    }
372    #[allow(dead_code)]
373    pub fn invalidate(&mut self, key: &str) {
374        if let Some(entry) = self.entries.get_mut(key) {
375            entry.valid = false;
376        }
377    }
378    #[allow(dead_code)]
379    pub fn clear(&mut self) {
380        self.entries.clear();
381    }
382    #[allow(dead_code)]
383    pub fn hit_rate(&self) -> f64 {
384        let total = self.hits + self.misses;
385        if total == 0 {
386            return 0.0;
387        }
388        self.hits as f64 / total as f64
389    }
390    #[allow(dead_code)]
391    pub fn size(&self) -> usize {
392        self.entries.len()
393    }
394}
395/// SPIR-V memory models.
396#[derive(Debug, Clone, PartialEq)]
397pub enum MemoryModel {
398    Simple,
399    GLSL450,
400    OpenCL,
401    Vulkan,
402}
403#[allow(dead_code)]
404#[derive(Debug, Clone)]
405pub struct SPIRVDepGraph {
406    pub(super) nodes: Vec<u32>,
407    pub(super) edges: Vec<(u32, u32)>,
408}
409impl SPIRVDepGraph {
410    #[allow(dead_code)]
411    pub fn new() -> Self {
412        SPIRVDepGraph {
413            nodes: Vec::new(),
414            edges: Vec::new(),
415        }
416    }
417    #[allow(dead_code)]
418    pub fn add_node(&mut self, id: u32) {
419        if !self.nodes.contains(&id) {
420            self.nodes.push(id);
421        }
422    }
423    #[allow(dead_code)]
424    pub fn add_dep(&mut self, dep: u32, dependent: u32) {
425        self.add_node(dep);
426        self.add_node(dependent);
427        self.edges.push((dep, dependent));
428    }
429    #[allow(dead_code)]
430    pub fn dependents_of(&self, node: u32) -> Vec<u32> {
431        self.edges
432            .iter()
433            .filter(|(d, _)| *d == node)
434            .map(|(_, dep)| *dep)
435            .collect()
436    }
437    #[allow(dead_code)]
438    pub fn dependencies_of(&self, node: u32) -> Vec<u32> {
439        self.edges
440            .iter()
441            .filter(|(_, dep)| *dep == node)
442            .map(|(d, _)| *d)
443            .collect()
444    }
445    #[allow(dead_code)]
446    pub fn topological_sort(&self) -> Vec<u32> {
447        let mut in_degree: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
448        for &n in &self.nodes {
449            in_degree.insert(n, 0);
450        }
451        for (_, dep) in &self.edges {
452            *in_degree.entry(*dep).or_insert(0) += 1;
453        }
454        let mut queue: std::collections::VecDeque<u32> = self
455            .nodes
456            .iter()
457            .filter(|&&n| in_degree[&n] == 0)
458            .copied()
459            .collect();
460        let mut result = Vec::new();
461        while let Some(node) = queue.pop_front() {
462            result.push(node);
463            for dep in self.dependents_of(node) {
464                let cnt = in_degree.entry(dep).or_insert(0);
465                *cnt = cnt.saturating_sub(1);
466                if *cnt == 0 {
467                    queue.push_back(dep);
468                }
469            }
470        }
471        result
472    }
473    #[allow(dead_code)]
474    pub fn has_cycle(&self) -> bool {
475        self.topological_sort().len() < self.nodes.len()
476    }
477}
478#[allow(dead_code)]
479#[derive(Debug, Clone, Default)]
480pub struct SPIRVPassStats {
481    pub total_runs: u32,
482    pub successful_runs: u32,
483    pub total_changes: u64,
484    pub time_ms: u64,
485    pub iterations_used: u32,
486}
487impl SPIRVPassStats {
488    #[allow(dead_code)]
489    pub fn new() -> Self {
490        Self::default()
491    }
492    #[allow(dead_code)]
493    pub fn record_run(&mut self, changes: u64, time_ms: u64, iterations: u32) {
494        self.total_runs += 1;
495        self.successful_runs += 1;
496        self.total_changes += changes;
497        self.time_ms += time_ms;
498        self.iterations_used = iterations;
499    }
500    #[allow(dead_code)]
501    pub fn average_changes_per_run(&self) -> f64 {
502        if self.total_runs == 0 {
503            return 0.0;
504        }
505        self.total_changes as f64 / self.total_runs as f64
506    }
507    #[allow(dead_code)]
508    pub fn success_rate(&self) -> f64 {
509        if self.total_runs == 0 {
510            return 0.0;
511        }
512        self.successful_runs as f64 / self.total_runs as f64
513    }
514    #[allow(dead_code)]
515    pub fn format_summary(&self) -> String {
516        format!(
517            "Runs: {}/{}, Changes: {}, Time: {}ms",
518            self.successful_runs, self.total_runs, self.total_changes, self.time_ms
519        )
520    }
521}
522/// SPIR-V type system.
523///
524/// Each type variant corresponds to one or more SPIR-V OpType* instructions.
525#[derive(Debug, Clone, PartialEq)]
526pub enum SpirVType {
527    /// OpTypeVoid — no value
528    Void,
529    /// OpTypeBool — true/false
530    Bool,
531    /// OpTypeInt — signed/unsigned integer, width in bits
532    Int {
533        /// Bit width: 8, 16, 32, or 64
534        width: u32,
535        /// 1 = signed, 0 = unsigned
536        signed: bool,
537    },
538    /// OpTypeFloat — IEEE floating-point, width in bits (16, 32, 64)
539    Float {
540        /// Bit width: 16, 32, or 64
541        width: u32,
542    },
543    /// OpTypeVector — fixed-length homogeneous vector
544    Vector {
545        /// Element type (must be scalar numeric)
546        element: Box<SpirVType>,
547        /// Number of components (2, 3, or 4)
548        count: u32,
549    },
550    /// OpTypeMatrix — column-major matrix of vectors
551    Matrix {
552        /// Column type (must be a vector)
553        column_type: Box<SpirVType>,
554        /// Number of columns
555        column_count: u32,
556    },
557    /// OpTypeArray — fixed-length array
558    Array {
559        /// Element type
560        element: Box<SpirVType>,
561        /// Length (must be a constant)
562        length: u32,
563    },
564    /// OpTypeRuntimeArray — runtime-length array (descriptor binding)
565    RuntimeArray(Box<SpirVType>),
566    /// OpTypeStruct — aggregate of named/indexed members
567    Struct(Vec<SpirVType>),
568    /// OpTypePointer — typed pointer with storage class
569    Pointer {
570        /// Storage class (e.g., Uniform, Input, Output, Function)
571        storage_class: StorageClass,
572        /// Pointed-to type
573        pointee: Box<SpirVType>,
574    },
575    /// OpTypeFunction — function signature
576    Function {
577        /// Return type
578        return_type: Box<SpirVType>,
579        /// Parameter types
580        param_types: Vec<SpirVType>,
581    },
582    /// OpTypeImage — image type for sampling/storage
583    Image {
584        /// Sampled element type
585        sampled_type: Box<SpirVType>,
586        /// Image dimensionality
587        dim: ImageDim,
588        /// Depth (0=no depth, 1=depth, 2=unknown)
589        depth: u32,
590        /// Arrayed (0=no, 1=yes)
591        arrayed: u32,
592        /// Multisampled (0=no, 1=yes)
593        ms: u32,
594        /// Sampled (1=sampled, 2=storage, 0=unknown)
595        sampled: u32,
596        /// Image format
597        format: ImageFormat,
598    },
599    /// OpTypeSampler — opaque sampler type
600    Sampler,
601    /// OpTypeSampledImage — combined image+sampler
602    SampledImage(Box<SpirVType>),
603}
604/// The SPIR-V code generation backend for OxiLean.
605///
606/// Translates OxiLean expressions and declarations into SPIR-V modules
607/// targeting Vulkan compute or graphics shaders.
608#[derive(Debug)]
609pub struct SpirVBackend {
610    /// The SPIR-V module being built
611    pub module: SpirVModule,
612    /// Map from type key to allocated type ID
613    pub(super) type_cache: HashMap<String, u32>,
614    /// Map from symbol name to ID
615    pub(super) symbol_table: HashMap<String, u32>,
616    /// ID of the imported GLSL.std.450 extended instruction set
617    pub(super) glsl_ext_id: Option<u32>,
618    /// ID of the void type
619    pub(super) void_type_id: Option<u32>,
620    /// ID of the bool type
621    pub(super) bool_type_id: Option<u32>,
622}
623impl SpirVBackend {
624    /// Create a new SPIR-V backend.
625    pub fn new() -> Self {
626        Self {
627            module: SpirVModule::new(),
628            type_cache: HashMap::new(),
629            symbol_table: HashMap::new(),
630            glsl_ext_id: None,
631            void_type_id: None,
632            bool_type_id: None,
633        }
634    }
635    /// Enable the Shader capability and GLSL 450 memory model (typical Vulkan setup).
636    pub fn configure_for_vulkan(&mut self) {
637        self.module.add_capability(SpirVCapability::Shader);
638        self.module.memory_model = (AddressingModel::Logical, MemoryModel::GLSL450);
639        let glsl_id = self.module.import_ext_inst("GLSL.std.450");
640        self.glsl_ext_id = Some(glsl_id);
641    }
642    /// Configure for OpenCL/compute kernels.
643    pub fn configure_for_opencl(&mut self) {
644        self.module.add_capability(SpirVCapability::Kernel);
645        self.module.add_capability(SpirVCapability::Addresses);
646        self.module.memory_model = (AddressingModel::Physical64, MemoryModel::OpenCL);
647    }
648    /// Declare or retrieve the void type.
649    pub fn get_void_type(&mut self) -> u32 {
650        if let Some(id) = self.void_type_id {
651            return id;
652        }
653        let id = self.module.fresh_id();
654        self.module.add_type(SpirVInstruction::with_result(
655            id,
656            0,
657            SpirVOp::Capability(SpirVCapability::Shader),
658            vec![],
659        ));
660        self.void_type_id = Some(id);
661        id
662    }
663    /// Declare a scalar integer type.
664    pub fn declare_int_type(&mut self, width: u32, signed: bool) -> u32 {
665        let key = format!("int_{}_{}", width, if signed { "s" } else { "u" });
666        if let Some(&id) = self.type_cache.get(&key) {
667            return id;
668        }
669        let id = self.module.fresh_id();
670        self.module.add_type(SpirVInstruction::with_result(
671            id,
672            0,
673            SpirVOp::Constant(width as u64),
674            vec![if signed { 1 } else { 0 }],
675        ));
676        self.type_cache.insert(key, id);
677        id
678    }
679    /// Declare a scalar float type.
680    pub fn declare_float_type(&mut self, width: u32) -> u32 {
681        let key = format!("float_{}", width);
682        if let Some(&id) = self.type_cache.get(&key) {
683            return id;
684        }
685        let id = self.module.fresh_id();
686        self.module.add_type(SpirVInstruction::with_result(
687            id,
688            0,
689            SpirVOp::Constant(width as u64),
690            vec![],
691        ));
692        self.type_cache.insert(key, id);
693        id
694    }
695    /// Declare a vector type.
696    pub fn declare_vector_type(&mut self, element_type_id: u32, count: u32) -> u32 {
697        let key = format!("vec_{}_{}", element_type_id, count);
698        if let Some(&id) = self.type_cache.get(&key) {
699            return id;
700        }
701        let id = self.module.fresh_id();
702        self.module.add_type(SpirVInstruction::with_result(
703            id,
704            0,
705            SpirVOp::CompositeConstruct,
706            vec![element_type_id, count],
707        ));
708        self.type_cache.insert(key, id);
709        id
710    }
711    /// Declare a pointer type.
712    pub fn declare_pointer_type(
713        &mut self,
714        storage_class: StorageClass,
715        pointee_type_id: u32,
716    ) -> u32 {
717        let key = format!("ptr_{:?}_{}", storage_class, pointee_type_id);
718        if let Some(&id) = self.type_cache.get(&key) {
719            return id;
720        }
721        let id = self.module.fresh_id();
722        self.module.add_type(SpirVInstruction::with_result(
723            id,
724            0,
725            SpirVOp::Variable(storage_class),
726            vec![pointee_type_id],
727        ));
728        self.type_cache.insert(key, id);
729        id
730    }
731    /// Declare a function type.
732    pub fn declare_function_type(&mut self, return_type_id: u32, param_type_ids: Vec<u32>) -> u32 {
733        let key = format!("fn_{}_[{}]", return_type_id, {
734            let s: Vec<String> = param_type_ids.iter().map(|i| i.to_string()).collect();
735            s.join(",")
736        });
737        if let Some(&id) = self.type_cache.get(&key) {
738            return id;
739        }
740        let id = self.module.fresh_id();
741        let mut operands = vec![return_type_id];
742        operands.extend_from_slice(&param_type_ids);
743        self.module.add_type(SpirVInstruction::with_result(
744            id,
745            0,
746            SpirVOp::Function,
747            operands,
748        ));
749        self.type_cache.insert(key, id);
750        id
751    }
752    /// Declare an integer constant.
753    pub fn declare_int_constant(&mut self, type_id: u32, value: u64) -> u32 {
754        let id = self.module.fresh_id();
755        self.module.add_constant(SpirVInstruction::with_result(
756            id,
757            type_id,
758            SpirVOp::Constant(value),
759            vec![value as u32, (value >> 32) as u32],
760        ));
761        id
762    }
763    /// Declare a float constant.
764    pub fn declare_float_constant(&mut self, type_id: u32, value: f32) -> u32 {
765        let id = self.module.fresh_id();
766        let bits = value.to_bits();
767        self.module.add_constant(SpirVInstruction::with_result(
768            id,
769            type_id,
770            SpirVOp::Constant(bits as u64),
771            vec![bits],
772        ));
773        id
774    }
775    /// Declare a bool constant.
776    pub fn declare_bool_constant(&mut self, type_id: u32, value: bool) -> u32 {
777        let id = self.module.fresh_id();
778        let op = if value {
779            SpirVOp::ConstantTrue
780        } else {
781            SpirVOp::ConstantFalse
782        };
783        self.module
784            .add_constant(SpirVInstruction::with_result(id, type_id, op, vec![]));
785        id
786    }
787    /// Declare a global variable.
788    pub fn declare_global_variable(
789        &mut self,
790        name: impl Into<String>,
791        type_id: u32,
792        storage_class: StorageClass,
793        decorations: Vec<Decoration>,
794    ) -> u32 {
795        let name = name.into();
796        let id = self.module.fresh_id();
797        self.module.set_name(id, name.clone());
798        for deco in decorations {
799            self.module.decorate(id, deco);
800        }
801        self.module.add_global_var(SpirVInstruction::with_result(
802            id,
803            type_id,
804            SpirVOp::Variable(storage_class),
805            vec![],
806        ));
807        self.symbol_table.insert(name, id);
808        id
809    }
810    /// Begin building a new function.
811    pub fn begin_function(
812        &mut self,
813        name: impl Into<String>,
814        return_type_id: u32,
815        param_type_ids: Vec<u32>,
816    ) -> SpirVFunction {
817        let name = name.into();
818        let func_id = self.module.fresh_id();
819        let func_type_id = self.declare_function_type(return_type_id, param_type_ids.clone());
820        self.module.set_name(func_id, name.clone());
821        self.symbol_table.insert(name.clone(), func_id);
822        let mut func = SpirVFunction::new(func_id, Some(name), return_type_id, func_type_id);
823        for &pt in &param_type_ids {
824            let pid = self.module.fresh_id();
825            func.add_param(pid, pt);
826        }
827        func
828    }
829    /// Finalize and add a function to the module.
830    pub fn finish_function(&mut self, func: SpirVFunction) {
831        self.module.add_function(func);
832    }
833    /// Emit the module as SPIR-V assembly text.
834    pub fn emit_text(&self) -> String {
835        self.module.emit_text()
836    }
837    /// Emit a minimal valid SPIR-V binary header (magic + version + generator + bound + schema).
838    pub fn emit_binary_header(&self) -> Vec<u32> {
839        vec![
840            0x0723_0203,
841            self.module.version,
842            self.module.generator,
843            self.module.bound,
844            0,
845        ]
846    }
847    /// Get the number of functions in the module.
848    pub fn function_count(&self) -> usize {
849        self.module.functions.len()
850    }
851    /// Look up a symbol by name.
852    pub fn lookup_symbol(&self, name: &str) -> Option<u32> {
853        self.symbol_table.get(name).copied()
854    }
855    /// Emit a compute shader skeleton for the given kernel name and local size.
856    pub fn emit_compute_kernel(
857        &mut self,
858        kernel_name: impl Into<String>,
859        local_size_x: u32,
860        local_size_y: u32,
861        local_size_z: u32,
862    ) -> u32 {
863        let kernel_name = kernel_name.into();
864        self.configure_for_vulkan();
865        self.module.add_capability(SpirVCapability::Shader);
866        let void_id = self.module.fresh_id();
867        let func_type_id = self.module.fresh_id();
868        let func_id = self.module.fresh_id();
869        let entry_block_id = self.module.fresh_id();
870        self.module.set_name(func_id, kernel_name.clone());
871        let mut func =
872            SpirVFunction::new(func_id, Some(kernel_name.clone()), void_id, func_type_id);
873        func.set_entry_point(ExecutionModel::GLCompute);
874        let mut entry_block = SpirVBasicBlock::new(entry_block_id);
875        entry_block.push(SpirVInstruction::no_result(SpirVOp::Return, vec![]));
876        func.add_block(entry_block);
877        self.module
878            .add_entry_point(ExecutionModel::GLCompute, func_id, &kernel_name, vec![]);
879        self.module.add_execution_mode(
880            func_id,
881            ExecutionMode::LocalSize(local_size_x, local_size_y, local_size_z),
882        );
883        self.module.add_function(func);
884        self.symbol_table.insert(kernel_name, func_id);
885        func_id
886    }
887}
888/// A complete SPIR-V module.
889///
890/// Contains all declarations required to produce a valid SPIR-V binary:
891/// capabilities, extensions, memory model, entry points, type declarations,
892/// global variables, constants, and function bodies.
893#[derive(Debug, Clone)]
894pub struct SpirVModule {
895    /// SPIR-V version (encoded as major<<16 | minor<<8)
896    pub version: u32,
897    /// Generator magic number
898    pub generator: u32,
899    /// Bound (next available ID)
900    pub bound: u32,
901    /// Declared capabilities
902    pub capabilities: Vec<SpirVCapability>,
903    /// Extension strings
904    pub extensions: Vec<String>,
905    /// Extended instruction set imports (name -> id)
906    pub ext_inst_imports: HashMap<String, u32>,
907    /// Addressing and memory model
908    pub memory_model: (AddressingModel, MemoryModel),
909    /// Entry points: (execution_model, function_id, name, interface_vars)
910    pub entry_points: Vec<(ExecutionModel, u32, String, Vec<u32>)>,
911    /// Execution modes per entry point
912    pub execution_modes: Vec<(u32, ExecutionMode)>,
913    /// Debug names (id -> name)
914    pub debug_names: HashMap<u32, String>,
915    /// Decorations (id -> list of decorations)
916    pub decorations: HashMap<u32, Vec<Decoration>>,
917    /// Type instructions (ordered by dependency)
918    pub types: Vec<SpirVInstruction>,
919    /// Constants
920    pub constants: Vec<SpirVInstruction>,
921    /// Global variables
922    pub global_vars: Vec<SpirVInstruction>,
923    /// Function definitions
924    pub functions: Vec<SpirVFunction>,
925}
926impl SpirVModule {
927    /// Create a new empty SPIR-V module targeting SPIR-V 1.6.
928    pub fn new() -> Self {
929        Self {
930            version: (1 << 16) | (6 << 8),
931            generator: 0x000D_0001,
932            bound: 1,
933            capabilities: Vec::new(),
934            extensions: Vec::new(),
935            ext_inst_imports: HashMap::new(),
936            memory_model: (AddressingModel::Logical, MemoryModel::GLSL450),
937            entry_points: Vec::new(),
938            execution_modes: Vec::new(),
939            debug_names: HashMap::new(),
940            decorations: HashMap::new(),
941            types: Vec::new(),
942            constants: Vec::new(),
943            global_vars: Vec::new(),
944            functions: Vec::new(),
945        }
946    }
947    /// Allocate a fresh ID.
948    pub fn fresh_id(&mut self) -> u32 {
949        let id = self.bound;
950        self.bound += 1;
951        id
952    }
953    /// Add a capability.
954    pub fn add_capability(&mut self, cap: SpirVCapability) {
955        if !self.capabilities.contains(&cap) {
956            self.capabilities.push(cap);
957        }
958    }
959    /// Add an extension.
960    pub fn add_extension(&mut self, ext: impl Into<String>) {
961        let ext = ext.into();
962        if !self.extensions.contains(&ext) {
963            self.extensions.push(ext);
964        }
965    }
966    /// Import an extended instruction set and return its ID.
967    pub fn import_ext_inst(&mut self, name: impl Into<String>) -> u32 {
968        let name = name.into();
969        if let Some(&id) = self.ext_inst_imports.get(&name) {
970            return id;
971        }
972        let id = self.fresh_id();
973        self.ext_inst_imports.insert(name, id);
974        id
975    }
976    /// Add an entry point.
977    pub fn add_entry_point(
978        &mut self,
979        model: ExecutionModel,
980        func_id: u32,
981        name: impl Into<String>,
982        interface_vars: Vec<u32>,
983    ) {
984        self.entry_points
985            .push((model, func_id, name.into(), interface_vars));
986    }
987    /// Add an execution mode for an entry point function.
988    pub fn add_execution_mode(&mut self, func_id: u32, mode: ExecutionMode) {
989        self.execution_modes.push((func_id, mode));
990    }
991    /// Assign a debug name to an ID.
992    pub fn set_name(&mut self, id: u32, name: impl Into<String>) {
993        self.debug_names.insert(id, name.into());
994    }
995    /// Add a decoration to an ID.
996    pub fn decorate(&mut self, id: u32, decoration: Decoration) {
997        self.decorations.entry(id).or_default().push(decoration);
998    }
999    /// Add a type instruction.
1000    pub fn add_type(&mut self, instr: SpirVInstruction) {
1001        self.types.push(instr);
1002    }
1003    /// Add a constant instruction.
1004    pub fn add_constant(&mut self, instr: SpirVInstruction) {
1005        self.constants.push(instr);
1006    }
1007    /// Add a global variable.
1008    pub fn add_global_var(&mut self, instr: SpirVInstruction) {
1009        self.global_vars.push(instr);
1010    }
1011    /// Add a function definition.
1012    pub fn add_function(&mut self, func: SpirVFunction) {
1013        self.functions.push(func);
1014    }
1015    /// Emit the module as SPIR-V assembly text.
1016    pub fn emit_text(&self) -> String {
1017        let mut lines = Vec::new();
1018        lines.push("; SPIR-V Module (OxiLean codegen)".to_string());
1019        lines.push(format!(
1020            "; Version: {}.{}",
1021            (self.version >> 16) & 0xFF,
1022            (self.version >> 8) & 0xFF
1023        ));
1024        lines.push(format!("; Bound: {}", self.bound));
1025        lines.push(String::new());
1026        for cap in &self.capabilities {
1027            lines.push(format!("OpCapability {:?}", cap));
1028        }
1029        for ext in &self.extensions {
1030            lines.push(format!("OpExtension \"{}\"", ext));
1031        }
1032        let mut sorted_imports: Vec<(&String, &u32)> = self.ext_inst_imports.iter().collect();
1033        sorted_imports.sort_by_key(|(_, &id)| id);
1034        for (name, id) in sorted_imports {
1035            lines.push(format!("%{} = OpExtInstImport \"{}\"", id, name));
1036        }
1037        lines.push(format!(
1038            "OpMemoryModel {:?} {:?}",
1039            self.memory_model.0, self.memory_model.1
1040        ));
1041        for (model, func_id, name, iface) in &self.entry_points {
1042            let iface_str: Vec<String> = iface.iter().map(|id| format!("%{}", id)).collect();
1043            lines.push(format!(
1044                "OpEntryPoint {:?} %{} \"{}\" {}",
1045                model,
1046                func_id,
1047                name,
1048                iface_str.join(" ")
1049            ));
1050        }
1051        for (func_id, mode) in &self.execution_modes {
1052            lines.push(format!("OpExecutionMode %{} {:?}", func_id, mode));
1053        }
1054        lines.push(String::new());
1055        lines.push("; Debug names".to_string());
1056        let mut sorted_names: Vec<(&u32, &String)> = self.debug_names.iter().collect();
1057        sorted_names.sort_by_key(|(&id, _)| id);
1058        for (id, name) in sorted_names {
1059            lines.push(format!("OpName %{} \"{}\"", id, name));
1060        }
1061        lines.push(String::new());
1062        lines.push("; Decorations".to_string());
1063        let mut sorted_decos: Vec<(&u32, &Vec<Decoration>)> = self.decorations.iter().collect();
1064        sorted_decos.sort_by_key(|(&id, _)| id);
1065        for (id, decos) in sorted_decos {
1066            for deco in decos {
1067                lines.push(format!("OpDecorate %{} {:?}", id, deco));
1068            }
1069        }
1070        lines.push(String::new());
1071        lines.push("; Types".to_string());
1072        for ty in &self.types {
1073            lines.push(ty.emit_text());
1074        }
1075        lines.push(String::new());
1076        lines.push("; Constants".to_string());
1077        for c in &self.constants {
1078            lines.push(c.emit_text());
1079        }
1080        lines.push(String::new());
1081        lines.push("; Global variables".to_string());
1082        for gv in &self.global_vars {
1083            lines.push(gv.emit_text());
1084        }
1085        lines.push(String::new());
1086        lines.push("; Functions".to_string());
1087        for func in &self.functions {
1088            lines.push(func.emit_text());
1089            lines.push(String::new());
1090        }
1091        lines.join("\n")
1092    }
1093    /// Estimate the total word count of the module binary.
1094    pub fn estimate_word_count(&self) -> u32 {
1095        let mut count = 5u32;
1096        for ty in &self.types {
1097            count += ty.word_count();
1098        }
1099        for c in &self.constants {
1100            count += c.word_count();
1101        }
1102        for gv in &self.global_vars {
1103            count += gv.word_count();
1104        }
1105        for func in &self.functions {
1106            count += 5 + 1;
1107            count += func.params.len() as u32 * 3;
1108            for block in &func.blocks {
1109                count += 2;
1110                for instr in &block.instructions {
1111                    count += instr.word_count();
1112                }
1113            }
1114        }
1115        count
1116    }
1117}
1118#[allow(dead_code)]
1119#[derive(Debug, Clone)]
1120pub struct SPIRVLivenessInfo {
1121    pub live_in: Vec<std::collections::HashSet<u32>>,
1122    pub live_out: Vec<std::collections::HashSet<u32>>,
1123    pub defs: Vec<std::collections::HashSet<u32>>,
1124    pub uses: Vec<std::collections::HashSet<u32>>,
1125}
1126impl SPIRVLivenessInfo {
1127    #[allow(dead_code)]
1128    pub fn new(block_count: usize) -> Self {
1129        SPIRVLivenessInfo {
1130            live_in: vec![std::collections::HashSet::new(); block_count],
1131            live_out: vec![std::collections::HashSet::new(); block_count],
1132            defs: vec![std::collections::HashSet::new(); block_count],
1133            uses: vec![std::collections::HashSet::new(); block_count],
1134        }
1135    }
1136    #[allow(dead_code)]
1137    pub fn add_def(&mut self, block: usize, var: u32) {
1138        if block < self.defs.len() {
1139            self.defs[block].insert(var);
1140        }
1141    }
1142    #[allow(dead_code)]
1143    pub fn add_use(&mut self, block: usize, var: u32) {
1144        if block < self.uses.len() {
1145            self.uses[block].insert(var);
1146        }
1147    }
1148    #[allow(dead_code)]
1149    pub fn is_live_in(&self, block: usize, var: u32) -> bool {
1150        self.live_in
1151            .get(block)
1152            .map(|s| s.contains(&var))
1153            .unwrap_or(false)
1154    }
1155    #[allow(dead_code)]
1156    pub fn is_live_out(&self, block: usize, var: u32) -> bool {
1157        self.live_out
1158            .get(block)
1159            .map(|s| s.contains(&var))
1160            .unwrap_or(false)
1161    }
1162}
1163/// SPIR-V storage classes for pointer types.
1164#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1165pub enum StorageClass {
1166    /// Shader uniform block binding
1167    Uniform,
1168    /// Storage buffer binding
1169    StorageBuffer,
1170    /// Push constant block
1171    PushConstant,
1172    /// Input variable (vertex/fragment attribute)
1173    Input,
1174    /// Output variable (vertex position, fragment color)
1175    Output,
1176    /// Function-local variable
1177    Function,
1178    /// Private (per-invocation, not shared)
1179    Private,
1180    /// Workgroup shared memory (compute)
1181    Workgroup,
1182    /// Cross-workgroup (global) memory
1183    CrossWorkgroup,
1184    /// Image (for storage images)
1185    Image,
1186    /// Generic pointer (OpenCL)
1187    Generic,
1188}
1189/// SPIR-V built-in variables.
1190#[derive(Debug, Clone, PartialEq)]
1191pub enum BuiltIn {
1192    Position,
1193    PointSize,
1194    ClipDistance,
1195    CullDistance,
1196    VertexId,
1197    InstanceId,
1198    PrimitiveId,
1199    InvocationId,
1200    Layer,
1201    ViewportIndex,
1202    TessLevelOuter,
1203    TessLevelInner,
1204    TessCoord,
1205    PatchVertices,
1206    FragCoord,
1207    PointCoord,
1208    FrontFacing,
1209    SampleId,
1210    SamplePosition,
1211    SampleMask,
1212    FragDepth,
1213    HelperInvocation,
1214    NumWorkgroups,
1215    WorkgroupSize,
1216    WorkgroupId,
1217    LocalInvocationId,
1218    GlobalInvocationId,
1219    LocalInvocationIndex,
1220    WorkDim,
1221    GlobalSize,
1222    EnqueuedWorkgroupSize,
1223    GlobalOffset,
1224    GlobalLinearId,
1225    SubgroupSize,
1226    SubgroupMaxSize,
1227    NumSubgroups,
1228    NumEnqueuedSubgroups,
1229    SubgroupId,
1230    SubgroupLocalInvocationId,
1231    VertexIndex,
1232    InstanceIndex,
1233}
1234/// GLSLstd450 extended instruction opcodes (a representative subset).
1235#[derive(Debug, Clone, PartialEq)]
1236pub enum GlslStd450Op {
1237    Round,
1238    RoundEven,
1239    Trunc,
1240    FAbs,
1241    SAbs,
1242    FSign,
1243    SSign,
1244    Floor,
1245    Ceil,
1246    Fract,
1247    Radians,
1248    Degrees,
1249    Sin,
1250    Cos,
1251    Tan,
1252    Asin,
1253    Acos,
1254    Atan,
1255    Atan2,
1256    Sinh,
1257    Cosh,
1258    Exp,
1259    Log,
1260    Exp2,
1261    Log2,
1262    Sqrt,
1263    InverseSqrt,
1264    Pow,
1265    FMin,
1266    UMin,
1267    SMin,
1268    FMax,
1269    UMax,
1270    SMax,
1271    FClamp,
1272    UClamp,
1273    SClamp,
1274    FMix,
1275    Step,
1276    SmoothStep,
1277    Fma,
1278    Length,
1279    Distance,
1280    Cross,
1281    Normalize,
1282    Reflect,
1283    Refract,
1284    FaceForward,
1285    MatrixInverse,
1286    ModfStruct,
1287    FrexpStruct,
1288    LdexpStruct,
1289    PackSnorm4x8,
1290    PackUnorm4x8,
1291    UnpackSnorm4x8,
1292    UnpackUnorm4x8,
1293}
1294#[allow(dead_code)]
1295#[derive(Debug, Clone)]
1296pub struct SPIRVDominatorTree {
1297    pub idom: Vec<Option<u32>>,
1298    pub dom_children: Vec<Vec<u32>>,
1299    pub dom_depth: Vec<u32>,
1300}
1301impl SPIRVDominatorTree {
1302    #[allow(dead_code)]
1303    pub fn new(size: usize) -> Self {
1304        SPIRVDominatorTree {
1305            idom: vec![None; size],
1306            dom_children: vec![Vec::new(); size],
1307            dom_depth: vec![0; size],
1308        }
1309    }
1310    #[allow(dead_code)]
1311    pub fn set_idom(&mut self, node: usize, idom: u32) {
1312        self.idom[node] = Some(idom);
1313    }
1314    #[allow(dead_code)]
1315    pub fn dominates(&self, a: usize, b: usize) -> bool {
1316        if a == b {
1317            return true;
1318        }
1319        let mut cur = b;
1320        loop {
1321            match self.idom[cur] {
1322                Some(parent) if parent as usize == a => return true,
1323                Some(parent) if parent as usize == cur => return false,
1324                Some(parent) => cur = parent as usize,
1325                None => return false,
1326            }
1327        }
1328    }
1329    #[allow(dead_code)]
1330    pub fn depth(&self, node: usize) -> u32 {
1331        self.dom_depth.get(node).copied().unwrap_or(0)
1332    }
1333}
1334/// Image dimensionality for OpTypeImage.
1335#[derive(Debug, Clone, PartialEq, Eq)]
1336pub enum ImageDim {
1337    Dim1D,
1338    Dim2D,
1339    Dim3D,
1340    Cube,
1341    Rect,
1342    Buffer,
1343    SubpassData,
1344}
1345#[allow(dead_code)]
1346pub struct SPIRVConstantFoldingHelper;
1347impl SPIRVConstantFoldingHelper {
1348    #[allow(dead_code)]
1349    pub fn fold_add_i64(a: i64, b: i64) -> Option<i64> {
1350        a.checked_add(b)
1351    }
1352    #[allow(dead_code)]
1353    pub fn fold_sub_i64(a: i64, b: i64) -> Option<i64> {
1354        a.checked_sub(b)
1355    }
1356    #[allow(dead_code)]
1357    pub fn fold_mul_i64(a: i64, b: i64) -> Option<i64> {
1358        a.checked_mul(b)
1359    }
1360    #[allow(dead_code)]
1361    pub fn fold_div_i64(a: i64, b: i64) -> Option<i64> {
1362        if b == 0 {
1363            None
1364        } else {
1365            a.checked_div(b)
1366        }
1367    }
1368    #[allow(dead_code)]
1369    pub fn fold_add_f64(a: f64, b: f64) -> f64 {
1370        a + b
1371    }
1372    #[allow(dead_code)]
1373    pub fn fold_mul_f64(a: f64, b: f64) -> f64 {
1374        a * b
1375    }
1376    #[allow(dead_code)]
1377    pub fn fold_neg_i64(a: i64) -> Option<i64> {
1378        a.checked_neg()
1379    }
1380    #[allow(dead_code)]
1381    pub fn fold_not_bool(a: bool) -> bool {
1382        !a
1383    }
1384    #[allow(dead_code)]
1385    pub fn fold_and_bool(a: bool, b: bool) -> bool {
1386        a && b
1387    }
1388    #[allow(dead_code)]
1389    pub fn fold_or_bool(a: bool, b: bool) -> bool {
1390        a || b
1391    }
1392    #[allow(dead_code)]
1393    pub fn fold_shl_i64(a: i64, b: u32) -> Option<i64> {
1394        a.checked_shl(b)
1395    }
1396    #[allow(dead_code)]
1397    pub fn fold_shr_i64(a: i64, b: u32) -> Option<i64> {
1398        a.checked_shr(b)
1399    }
1400    #[allow(dead_code)]
1401    pub fn fold_rem_i64(a: i64, b: i64) -> Option<i64> {
1402        if b == 0 {
1403            None
1404        } else {
1405            Some(a % b)
1406        }
1407    }
1408    #[allow(dead_code)]
1409    pub fn fold_bitand_i64(a: i64, b: i64) -> i64 {
1410        a & b
1411    }
1412    #[allow(dead_code)]
1413    pub fn fold_bitor_i64(a: i64, b: i64) -> i64 {
1414        a | b
1415    }
1416    #[allow(dead_code)]
1417    pub fn fold_bitxor_i64(a: i64, b: i64) -> i64 {
1418        a ^ b
1419    }
1420    #[allow(dead_code)]
1421    pub fn fold_bitnot_i64(a: i64) -> i64 {
1422        !a
1423    }
1424}
1425/// SPIR-V opcodes (a representative subset).
1426///
1427/// Each variant corresponds to a SPIR-V instruction from the specification.
1428/// Numeric opcode values follow the SPIR-V 1.6 spec (Table 1).
1429#[derive(Debug, Clone, PartialEq)]
1430pub enum SpirVOp {
1431    /// OpVariable: declare a variable with storage class
1432    Variable(StorageClass),
1433    /// OpLoad: load from a pointer
1434    Load,
1435    /// OpStore: store to a pointer
1436    Store,
1437    /// OpAccessChain: compute a pointer into a composite
1438    AccessChain,
1439    /// OpCopyObject: copy a value
1440    CopyObject,
1441    /// OpIAdd: integer addition
1442    IAdd,
1443    /// OpISub: integer subtraction
1444    ISub,
1445    /// OpIMul: integer multiplication
1446    IMul,
1447    /// OpSDiv: signed integer division
1448    SDiv,
1449    /// OpUDiv: unsigned integer division
1450    UDiv,
1451    /// OpSMod: signed modulo
1452    SMod,
1453    /// OpUMod: unsigned modulo
1454    UMod,
1455    /// OpSNegate: signed negation
1456    SNegate,
1457    /// OpFAdd: float addition
1458    FAdd,
1459    /// OpFSub: float subtraction
1460    FSub,
1461    /// OpFMul: float multiplication
1462    FMul,
1463    /// OpFDiv: float division
1464    FDiv,
1465    /// OpFMod: float modulo
1466    FMod,
1467    /// OpFNegate: float negation
1468    FNegate,
1469    /// OpFRem: float remainder
1470    FRem,
1471    /// OpIEqual: integer equality
1472    IEqual,
1473    /// OpINotEqual: integer inequality
1474    INotEqual,
1475    /// OpSLessThan: signed less-than
1476    SLessThan,
1477    /// OpSLessThanEqual: signed less-than-or-equal
1478    SLessThanEqual,
1479    /// OpSGreaterThan: signed greater-than
1480    SGreaterThan,
1481    /// OpULessThan: unsigned less-than
1482    ULessThan,
1483    /// OpFOrdEqual: float ordered equal
1484    FOrdEqual,
1485    /// OpFOrdLessThan: float ordered less-than
1486    FOrdLessThan,
1487    /// OpFOrdGreaterThan: float ordered greater-than
1488    FOrdGreaterThan,
1489    /// OpLogicalAnd: logical and
1490    LogicalAnd,
1491    /// OpLogicalOr: logical or
1492    LogicalOr,
1493    /// OpLogicalNot: logical not
1494    LogicalNot,
1495    /// OpLogicalEqual: logical equality
1496    LogicalEqual,
1497    /// OpBitwiseAnd: bitwise and
1498    BitwiseAnd,
1499    /// OpBitwiseOr: bitwise or
1500    BitwiseOr,
1501    /// OpBitwiseXor: bitwise xor
1502    BitwiseXor,
1503    /// OpNot: bitwise not
1504    Not,
1505    /// OpShiftLeftLogical: logical shift left
1506    ShiftLeftLogical,
1507    /// OpShiftRightLogical: logical shift right
1508    ShiftRightLogical,
1509    /// OpShiftRightArithmetic: arithmetic shift right
1510    ShiftRightArithmetic,
1511    /// OpConvertFToS: float-to-signed-int
1512    ConvertFToS,
1513    /// OpConvertFToU: float-to-unsigned-int
1514    ConvertFToU,
1515    /// OpConvertSToF: signed-int-to-float
1516    ConvertSToF,
1517    /// OpConvertUToF: unsigned-int-to-float
1518    ConvertUToF,
1519    /// OpFConvert: float-to-float conversion
1520    FConvert,
1521    /// OpSConvert: signed integer bit-width conversion
1522    SConvert,
1523    /// OpUConvert: unsigned integer bit-width conversion
1524    UConvert,
1525    /// OpBitcast: reinterpret bits as another type
1526    Bitcast,
1527    /// OpCompositeConstruct: build a vector/matrix/struct/array
1528    CompositeConstruct,
1529    /// OpCompositeExtract: extract a component from a composite
1530    CompositeExtract,
1531    /// OpCompositeInsert: insert a value into a composite
1532    CompositeInsert,
1533    /// OpVectorShuffle: permute vector components
1534    VectorShuffle,
1535    /// OpVectorExtractDynamic: extract at runtime index
1536    VectorExtractDynamic,
1537    /// OpVectorInsertDynamic: insert at runtime index
1538    VectorInsertDynamic,
1539    /// OpMatrixTimesVector: mat * vec
1540    MatrixTimesVector,
1541    /// OpVectorTimesMatrix: vec * mat
1542    VectorTimesMatrix,
1543    /// OpMatrixTimesMatrix: mat * mat
1544    MatrixTimesMatrix,
1545    /// OpMatrixTimesScalar: mat * scalar
1546    MatrixTimesScalar,
1547    /// OpDot: dot product of two vectors
1548    Dot,
1549    /// OpOuterProduct: outer product of two vectors
1550    OuterProduct,
1551    /// OpTranspose: matrix transpose
1552    Transpose,
1553    /// OpLabel: basic block label
1554    Label,
1555    /// OpBranch: unconditional branch
1556    Branch,
1557    /// OpBranchConditional: conditional branch
1558    BranchConditional,
1559    /// OpSwitch: switch on integer
1560    Switch,
1561    /// OpReturn: return from function with void
1562    Return,
1563    /// OpReturnValue: return a value
1564    ReturnValue,
1565    /// OpUnreachable: mark unreachable code
1566    Unreachable,
1567    /// OpPhi: SSA phi node
1568    Phi,
1569    /// OpLoopMerge: loop structure hint
1570    LoopMerge,
1571    /// OpSelectionMerge: selection structure hint
1572    SelectionMerge,
1573    /// OpFunction: begin a function definition
1574    Function,
1575    /// OpFunctionParameter: declare a parameter
1576    FunctionParameter,
1577    /// OpFunctionEnd: end a function definition
1578    FunctionEnd,
1579    /// OpFunctionCall: call a function
1580    FunctionCall,
1581    /// OpImageSampleImplicitLod: sample image with implicit LOD
1582    ImageSampleImplicitLod,
1583    /// OpImageSampleExplicitLod: sample image with explicit LOD
1584    ImageSampleExplicitLod,
1585    /// OpImageLoad: load from a storage image
1586    ImageLoad,
1587    /// OpImageStore: store to a storage image
1588    ImageStore,
1589    /// OpAtomicLoad: atomic load
1590    AtomicLoad,
1591    /// OpAtomicStore: atomic store
1592    AtomicStore,
1593    /// OpAtomicIAdd: atomic integer add
1594    AtomicIAdd,
1595    /// OpAtomicISub: atomic integer subtract
1596    AtomicISub,
1597    /// OpAtomicCompareExchange: compare and swap
1598    AtomicCompareExchange,
1599    /// OpExtInst (GLSLstd450) — standard math functions
1600    ExtInstGlsl(GlslStd450Op),
1601    /// OpCapability: declare a SPIR-V capability
1602    Capability(SpirVCapability),
1603    /// OpExtension: import a SPIR-V extension
1604    Extension(String),
1605    /// OpExtInstImport: import extended instruction set
1606    ExtInstImport(String),
1607    /// OpMemoryModel: set addressing + memory model
1608    MemoryModel(AddressingModel, MemoryModel),
1609    /// OpEntryPoint: declare a shader entry point
1610    EntryPoint(ExecutionModel, String),
1611    /// OpExecutionMode: declare execution mode for entry point
1612    ExecutionMode(ExecutionMode),
1613    /// OpDecorate: decorate an ID with metadata
1614    Decorate(Decoration),
1615    /// OpMemberDecorate: decorate a struct member
1616    MemberDecorate(u32, Decoration),
1617    /// OpName: assign debug name to an ID
1618    Name(String),
1619    /// OpConstant: scalar constant value
1620    Constant(u64),
1621    /// OpConstantComposite: composite constant
1622    ConstantComposite,
1623    /// OpConstantTrue: boolean true
1624    ConstantTrue,
1625    /// OpConstantFalse: boolean false
1626    ConstantFalse,
1627    /// OpTypeForwardPointer: forward declaration of pointer type
1628    TypeForwardPointer(StorageClass),
1629    /// OpControlBarrier: synchronization barrier
1630    ControlBarrier,
1631    /// OpMemoryBarrier: memory barrier
1632    MemoryBarrier,
1633}
1634/// SPIR-V execution modes.
1635#[derive(Debug, Clone, PartialEq)]
1636pub enum ExecutionMode {
1637    Invocations(u32),
1638    SpacingEqual,
1639    SpacingFractionalEven,
1640    SpacingFractionalOdd,
1641    VertexOrderCw,
1642    VertexOrderCcw,
1643    PixelCenterInteger,
1644    OriginUpperLeft,
1645    OriginLowerLeft,
1646    EarlyFragmentTests,
1647    PointMode,
1648    Xfb,
1649    DepthReplacing,
1650    DepthGreater,
1651    DepthLess,
1652    DepthUnchanged,
1653    LocalSize(u32, u32, u32),
1654    LocalSizeHint(u32, u32, u32),
1655    InputPoints,
1656    InputLines,
1657    InputLinesAdjacency,
1658    Triangles,
1659    InputTrianglesAdjacency,
1660    Quads,
1661    Isolines,
1662    OutputVertices(u32),
1663    OutputPoints,
1664    OutputLineStrip,
1665    OutputTriangleStrip,
1666}
1667/// A SPIR-V function definition.
1668#[derive(Debug, Clone)]
1669pub struct SpirVFunction {
1670    /// Function result ID
1671    pub id: u32,
1672    /// Function return type ID
1673    pub return_type_id: u32,
1674    /// Function type ID (full signature)
1675    pub function_type_id: u32,
1676    /// Debug name (from OpName)
1677    pub name: Option<String>,
1678    /// Parameter IDs (one per OpFunctionParameter)
1679    pub params: Vec<(u32, u32)>,
1680    /// Basic blocks (first is entry block)
1681    pub blocks: Vec<SpirVBasicBlock>,
1682    /// Whether this is an entry point
1683    pub is_entry_point: bool,
1684    /// Execution model (if entry point)
1685    pub execution_model: Option<ExecutionModel>,
1686}
1687impl SpirVFunction {
1688    /// Create a new function.
1689    pub fn new(id: u32, name: Option<String>, return_type_id: u32, function_type_id: u32) -> Self {
1690        Self {
1691            id,
1692            return_type_id,
1693            function_type_id,
1694            name,
1695            params: Vec::new(),
1696            blocks: Vec::new(),
1697            is_entry_point: false,
1698            execution_model: None,
1699        }
1700    }
1701    /// Add a parameter (param_id, type_id).
1702    pub fn add_param(&mut self, param_id: u32, type_id: u32) {
1703        self.params.push((param_id, type_id));
1704    }
1705    /// Add a basic block.
1706    pub fn add_block(&mut self, block: SpirVBasicBlock) {
1707        self.blocks.push(block);
1708    }
1709    /// Mark as an entry point.
1710    pub fn set_entry_point(&mut self, model: ExecutionModel) {
1711        self.is_entry_point = true;
1712        self.execution_model = Some(model);
1713    }
1714    /// Emit the function as SPIR-V assembly text.
1715    pub fn emit_text(&self) -> String {
1716        let mut lines = Vec::new();
1717        let name_comment = self
1718            .name
1719            .as_deref()
1720            .map(|n| format!(" ; {}", n))
1721            .unwrap_or_default();
1722        lines.push(format!(
1723            "%{} = OpFunction %{} None %{}{}",
1724            self.id, self.return_type_id, self.function_type_id, name_comment
1725        ));
1726        for (pid, tid) in &self.params {
1727            lines.push(format!("  %{} = OpFunctionParameter %{}", pid, tid));
1728        }
1729        for block in &self.blocks {
1730            lines.push(block.emit_text());
1731        }
1732        lines.push("OpFunctionEnd".to_string());
1733        lines.join("\n")
1734    }
1735    /// Get the number of instructions across all blocks.
1736    pub fn total_instrs(&self) -> usize {
1737        self.blocks.iter().map(|b| b.instr_count()).sum()
1738    }
1739}
1740#[allow(dead_code)]
1741#[derive(Debug, Clone, PartialEq)]
1742pub enum SPIRVPassPhase {
1743    Analysis,
1744    Transformation,
1745    Verification,
1746    Cleanup,
1747}
1748impl SPIRVPassPhase {
1749    #[allow(dead_code)]
1750    pub fn name(&self) -> &str {
1751        match self {
1752            SPIRVPassPhase::Analysis => "analysis",
1753            SPIRVPassPhase::Transformation => "transformation",
1754            SPIRVPassPhase::Verification => "verification",
1755            SPIRVPassPhase::Cleanup => "cleanup",
1756        }
1757    }
1758    #[allow(dead_code)]
1759    pub fn is_modifying(&self) -> bool {
1760        matches!(
1761            self,
1762            SPIRVPassPhase::Transformation | SPIRVPassPhase::Cleanup
1763        )
1764    }
1765}
1766#[allow(dead_code)]
1767#[derive(Debug, Clone)]
1768pub struct SPIRVWorklist {
1769    pub(super) items: std::collections::VecDeque<u32>,
1770    pub(super) in_worklist: std::collections::HashSet<u32>,
1771}
1772impl SPIRVWorklist {
1773    #[allow(dead_code)]
1774    pub fn new() -> Self {
1775        SPIRVWorklist {
1776            items: std::collections::VecDeque::new(),
1777            in_worklist: std::collections::HashSet::new(),
1778        }
1779    }
1780    #[allow(dead_code)]
1781    pub fn push(&mut self, item: u32) -> bool {
1782        if self.in_worklist.insert(item) {
1783            self.items.push_back(item);
1784            true
1785        } else {
1786            false
1787        }
1788    }
1789    #[allow(dead_code)]
1790    pub fn pop(&mut self) -> Option<u32> {
1791        let item = self.items.pop_front()?;
1792        self.in_worklist.remove(&item);
1793        Some(item)
1794    }
1795    #[allow(dead_code)]
1796    pub fn is_empty(&self) -> bool {
1797        self.items.is_empty()
1798    }
1799    #[allow(dead_code)]
1800    pub fn len(&self) -> usize {
1801        self.items.len()
1802    }
1803    #[allow(dead_code)]
1804    pub fn contains(&self, item: u32) -> bool {
1805        self.in_worklist.contains(&item)
1806    }
1807}