1use std::{
41 borrow::Cow,
42 collections::{BTreeMap, HashMap},
43};
44
45use snafu::prelude::*;
46use wgsl_rs_ir as ir;
47
48use crate::Source;
49
50#[derive(Snafu, Debug)]
52pub enum Error {
53 #[snafu(display(
54 "Unable to resolve template '{template_name}' for type args {:?}; available templates: \
55 {:?}",
56 mangled_type_args,
57 available
58 ))]
59 TemplateResolution {
60 template_name: String,
61 mangled_type_args: Vec<String>,
62 available: Vec<String>,
63 },
64
65 #[snafu(display(
66 "{module}::bind_group_{group}: missing binding resource for declared name {name:?}"
67 ))]
68 MissingBinding {
69 module: &'static str,
70 group: u32,
71 name: String,
72 },
73
74 #[snafu(display(
75 "{module}::bind_group_{group}: resource provided for unknown binding name {name:?} \
76 (declared names: {declared:?})"
77 ))]
78 UnknownBindingName {
79 module: &'static str,
80 group: u32,
81 name: String,
82 declared: Vec<String>,
83 },
84
85 #[snafu(display(
86 "{module}::bind_group_{group}: binding name {name:?} appears more than once in resources"
87 ))]
88 DuplicateBindingName {
89 module: &'static str,
90 group: u32,
91 name: String,
92 },
93
94 #[snafu(display(
95 "{module}: no bind group @group({group}) declared; available groups: {available:?}"
96 ))]
97 NoSuchBindGroup {
98 module: &'static str,
99 group: u32,
100 available: Vec<u32>,
101 },
102}
103
104impl From<crate::SourceError<'_>> for Error {
105 fn from(err: crate::SourceError<'_>) -> Self {
106 match err {
107 crate::SourceError::TemplateWgsl {
108 uninstantiated_source,
109 } => Error::TemplateResolution {
110 template_name: format!("<source {}>", uninstantiated_source.name),
111 mangled_type_args: uninstantiated_source
112 .module_type_params
113 .iter()
114 .map(|s| (*s).to_string())
115 .collect(),
116 available: Vec::new(),
117 },
118 crate::SourceError::TemplateNotFound {
119 template_name,
120 mangled_type_args,
121 available_templates,
122 } => Error::TemplateResolution {
123 template_name,
124 mangled_type_args,
125 available: available_templates,
126 },
127 crate::SourceError::AmbiguousTemplate {
128 template_name,
129 mangled_type_args,
130 matching_source_names,
131 available_templates: _,
132 } => Error::TemplateResolution {
133 template_name,
134 mangled_type_args,
135 available: matching_source_names,
136 },
137 }
138 }
139}
140
141#[derive(Clone, Debug)]
147pub struct WgpuLinkage {
148 pub module_label: &'static str,
150
151 ir: ir::Module,
159
160 pub bind_groups: BTreeMap<u32, BindGroupInfo>,
163
164 bind_group_layouts: HashMap<u32, wgpu::BindGroupLayout>,
169
170 pipeline_layout: Option<wgpu::PipelineLayout>,
173
174 pub vertex_entries: Vec<EntryPointInfo>,
176
177 pub fragment_entries: Vec<EntryPointInfo>,
179
180 pub compute_entries: Vec<ComputeEntryInfo>,
182
183 pub buffers: Vec<BufferDescriptorInfo>,
186}
187
188impl core::fmt::Display for WgpuLinkage {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 let WgpuLinkage {
191 module_label,
192 ir,
193 bind_groups,
194 bind_group_layouts,
195 pipeline_layout,
196 vertex_entries,
197 fragment_entries,
198 compute_entries,
199 buffers,
200 } = self;
201
202 writeln!(
203 f,
204 "linkage for '{module_label}' ({} IR items)",
205 ir.items.len()
206 )?;
207
208 writeln!(f, " entry points:")?;
209 if vertex_entries.is_empty() && fragment_entries.is_empty() && compute_entries.is_empty() {
210 writeln!(f, " (none)")?;
211 }
212 for e in vertex_entries {
213 writeln!(f, " vertex: {}", e.name)?;
214 }
215 for e in fragment_entries {
216 writeln!(f, " fragment: {}", e.name)?;
217 }
218 for e in compute_entries {
219 let (x, y, z) = e.workgroup_size;
220 writeln!(f, " compute: {} @workgroup_size({x}, {y}, {z})", e.name)?;
221 }
222
223 writeln!(f, " bind groups:")?;
224 if bind_groups.is_empty() {
225 writeln!(f, " (none)")?;
226 }
227 for (group, info) in bind_groups {
228 writeln!(f, " @group({group}) {}", info.label)?;
229 for meta in &info.bindings {
230 writeln!(
231 f,
232 " @binding({}) {} ({})",
233 meta.binding,
234 meta.name,
235 binding_kind_label(&meta.kind)
236 )?;
237 }
238 }
239
240 writeln!(f, " buffers:")?;
241 if buffers.is_empty() {
242 writeln!(f, " (none)")?;
243 }
244 for b in buffers {
245 let size = if b.size == 0 {
248 "runtime-sized".to_string()
249 } else {
250 format!("{} bytes", b.size)
251 };
252 writeln!(
253 f,
254 " @group({}) @binding({}): {} ({}, {size}, {ty})",
255 b.group,
256 b.binding,
257 b.binding_name,
258 buffer_kind_label(&b.kind),
259 ty = b.ty
260 )?;
261 }
262
263 write!(
266 f,
267 " cache: {} of {} bind group layouts built, pipeline layout {}",
268 bind_group_layouts.len(),
269 bind_groups.len(),
270 if pipeline_layout.is_some() {
271 "built"
272 } else {
273 "not built"
274 }
275 )?;
276
277 Ok(())
278 }
279}
280
281fn binding_kind_label(kind: &BindingKind) -> &'static str {
283 match kind {
284 BindingKind::Uniform => "uniform",
285 BindingKind::Storage { read_only: true } => "storage, read-only",
286 BindingKind::Storage { read_only: false } => "storage, read-write",
287 BindingKind::Sampler { comparison: true } => "comparison sampler",
288 BindingKind::Sampler { comparison: false } => "sampler",
289 BindingKind::Texture => "texture",
290 BindingKind::DepthTexture => "depth texture",
291 BindingKind::StorageTexture { read_write: true } => "storage texture, read-write",
292 BindingKind::StorageTexture { read_write: false } => "storage texture",
293 }
294}
295
296fn buffer_kind_label(kind: &BufferKind) -> &'static str {
298 match kind {
299 BufferKind::Uniform => "uniform",
300 BufferKind::Storage { read_only: true } => "storage, read-only",
301 BufferKind::Storage { read_only: false } => "storage, read-write",
302 }
303}
304
305impl WgpuLinkage {
306 pub fn bind_group(&self, group: u32) -> Option<&BindGroupInfo> {
308 self.bind_groups.get(&group)
309 }
310
311 pub fn buffer(&self, name: &str) -> Option<&BufferDescriptorInfo> {
314 self.buffers.iter().find(|b| b.binding_name == name)
315 }
316
317 pub fn take_buffer(&mut self, name: &str) -> Option<BufferDescriptorInfo> {
323 let mut info = None;
324 self.buffers.retain(|bi| {
325 if bi.binding_name == name {
326 info = Some(bi.clone());
327 false
328 } else {
329 true
330 }
331 });
332 info
333 }
334
335 pub fn wgsl_source(&self) -> String {
343 ir::render_module(&self.ir)
344 }
345
346 pub fn shader_module_descriptor(&self) -> wgpu::ShaderModuleDescriptor<'static> {
351 wgpu::ShaderModuleDescriptor {
352 label: Some(self.module_label),
353 source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Owned(self.wgsl_source())),
354 }
355 }
356
357 pub fn shader_module(&self, device: &wgpu::Device) -> wgpu::ShaderModule {
360 device.create_shader_module(self.shader_module_descriptor())
361 }
362
363 pub fn vertex_entry(&self, name: &str) -> Option<&EntryPointInfo> {
365 self.vertex_entries.iter().find(|e| e.name == name)
366 }
367
368 pub fn fragment_entry(&self, name: &str) -> Option<&EntryPointInfo> {
370 self.fragment_entries.iter().find(|e| e.name == name)
371 }
372
373 pub fn compute_entry(&self, name: &str) -> Option<&ComputeEntryInfo> {
375 self.compute_entries.iter().find(|e| e.name == name)
376 }
377
378 pub fn bind_group_layout(
386 &mut self,
387 group: u32,
388 device: &wgpu::Device,
389 ) -> Option<wgpu::BindGroupLayout> {
390 if let Some(layout) = self.bind_group_layouts.get(&group) {
391 return Some(layout.clone());
392 }
393 let info = self.bind_groups.get(&group)?;
394 let layout = info.layout(device);
395 self.bind_group_layouts.insert(group, layout.clone());
396 Some(layout)
397 }
398
399 pub fn create_bind_group_named<'a>(
420 &mut self,
421 group: u32,
422 device: &wgpu::Device,
423 resources: &[(&str, wgpu::BindingResource<'a>)],
424 ) -> Result<wgpu::BindGroup, Error> {
425 if !self.bind_group_layouts.contains_key(&group) {
429 let Some(info) = self.bind_groups.get(&group) else {
430 let mut available: Vec<u32> = self.bind_groups.keys().copied().collect();
431 available.sort_unstable();
432 return NoSuchBindGroupSnafu {
433 module: self.module_label,
434 group,
435 available,
436 }
437 .fail();
438 };
439 self.bind_group_layouts.insert(group, info.layout(device));
440 }
441 let info = self
442 .bind_groups
443 .get(&group)
444 .expect("just inserted or found");
445 let layout = self
446 .bind_group_layouts
447 .get(&group)
448 .expect("just inserted or found");
449 info.create_named(self.module_label, device, layout, resources)
450 }
451
452 pub fn pipeline_layout(
463 &mut self,
464 device: &wgpu::Device,
465 label: Option<&str>,
466 ) -> wgpu::PipelineLayout {
467 if let Some(layout) = &self.pipeline_layout {
468 return layout.clone();
469 }
470 for (&group, info) in &self.bind_groups {
472 self.bind_group_layouts
473 .entry(group)
474 .or_insert_with(|| info.layout(device));
475 }
476 let max_group = self.bind_group_layouts.keys().copied().max().unwrap_or(0);
482 let mut bg_layouts: Vec<Option<&wgpu::BindGroupLayout>> = (0..=max_group)
483 .map(|g| self.bind_group_layouts.get(&g))
484 .collect();
485 while bg_layouts.last() == Some(&None) {
487 bg_layouts.pop();
488 }
489 let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
490 label,
491 bind_group_layouts: &bg_layouts,
492 immediate_size: 0,
493 });
494 self.pipeline_layout = Some(layout.clone());
495 layout
496 }
497}
498
499pub trait IrModuleExt {
509 fn generate_linkage(&self) -> WgpuLinkage;
513
514 #[cfg(feature = "validation")]
521 fn validate(&self) -> Result<(), String>;
522}
523
524impl IrModuleExt for wgsl_rs_ir::Module {
525 fn generate_linkage(&self) -> WgpuLinkage {
526 analyze_ir_module(self.clone())
527 }
528
529 #[cfg(feature = "validation")]
530 fn validate(&self) -> Result<(), String> {
531 crate::validate_wgsl_source(&self.wgsl_source())
532 }
533}
534
535#[derive(Clone, Debug)]
540pub struct BindGroupInfo {
541 pub group: u32,
543 pub entries: Vec<wgpu::BindGroupLayoutEntry>,
545 pub bindings: Vec<BindingMeta>,
548 pub label: String,
550}
551
552#[derive(Clone, Debug)]
554pub struct BindingMeta {
555 pub name: String,
557 pub binding: u32,
559 pub kind: BindingKind,
561}
562
563#[derive(Clone, Copy, Debug, PartialEq, Eq)]
565pub enum BindingKind {
566 Uniform,
568 Storage { read_only: bool },
570 Sampler { comparison: bool },
572 Texture,
574 DepthTexture,
576 StorageTexture { read_write: bool },
579}
580
581impl BindGroupInfo {
582 pub fn layout_descriptor<'a>(&'a self) -> wgpu::BindGroupLayoutDescriptor<'a> {
586 wgpu::BindGroupLayoutDescriptor {
587 label: Some(&self.label),
588 entries: &self.entries,
589 }
590 }
591
592 pub fn layout(&self, device: &wgpu::Device) -> wgpu::BindGroupLayout {
594 device.create_bind_group_layout(&self.layout_descriptor())
595 }
596
597 pub fn create<'a>(
602 &self,
603 device: &wgpu::Device,
604 layout: &wgpu::BindGroupLayout,
605 resources: &[wgpu::BindingResource<'a>],
606 ) -> wgpu::BindGroup {
607 assert_eq!(
608 resources.len(),
609 self.entries.len(),
610 "{}: expected {} binding resource(s), got {}",
611 self.label,
612 self.entries.len(),
613 resources.len(),
614 );
615 let entries: Vec<wgpu::BindGroupEntry> = self
616 .entries
617 .iter()
618 .zip(resources.iter())
619 .map(|(layout_entry, resource)| wgpu::BindGroupEntry {
620 binding: layout_entry.binding,
621 resource: resource.clone(),
622 })
623 .collect();
624 device.create_bind_group(&wgpu::BindGroupDescriptor {
625 label: Some(&self.label),
626 layout,
627 entries: &entries,
628 })
629 }
630
631 pub fn create_named<'a>(
649 &self,
650 module_label: &'static str,
651 device: &wgpu::Device,
652 layout: &wgpu::BindGroupLayout,
653 resources: &[(&str, wgpu::BindingResource<'a>)],
654 ) -> Result<wgpu::BindGroup, Error> {
655 use std::collections::HashMap;
656
657 let mut by_name: HashMap<&str, wgpu::BindingResource<'a>> =
659 HashMap::with_capacity(resources.len());
660 for &(name, ref resource) in resources {
661 if by_name.insert(name, resource.clone()).is_some() {
662 return DuplicateBindingNameSnafu {
663 module: module_label,
664 group: self.group,
665 name: name.to_string(),
666 }
667 .fail();
668 }
669 }
670
671 for binding in &self.bindings {
673 if !by_name.contains_key(binding.name.as_str()) {
674 return MissingBindingSnafu {
675 module: module_label,
676 group: self.group,
677 name: binding.name.clone(),
678 }
679 .fail();
680 }
681 }
682
683 let mut hit: HashMap<&str, ()> = HashMap::with_capacity(by_name.len());
687 let mut entries: Vec<wgpu::BindGroupEntry> = Vec::with_capacity(self.bindings.len());
688 for (layout_entry, binding) in self.entries.iter().zip(self.bindings.iter()) {
689 let resource = by_name
690 .get(binding.name.as_str())
691 .expect("checked above")
692 .clone();
693 hit.insert(binding.name.as_str(), ());
694 entries.push(wgpu::BindGroupEntry {
695 binding: layout_entry.binding,
696 resource,
697 });
698 }
699 for name in by_name.keys() {
700 if !hit.contains_key(name) {
701 let declared: Vec<String> = self.bindings.iter().map(|b| b.name.clone()).collect();
702 return UnknownBindingNameSnafu {
703 module: module_label,
704 group: self.group,
705 name: (*name).to_string(),
706 declared,
707 }
708 .fail();
709 }
710 }
711
712 Ok(device.create_bind_group(&wgpu::BindGroupDescriptor {
713 label: Some(&self.label),
714 layout,
715 entries: &entries,
716 }))
717 }
718}
719
720#[derive(Clone, Debug)]
724pub struct EntryPointInfo {
725 pub name: Cow<'static, str>,
730}
731
732impl EntryPointInfo {
733 pub fn vertex_state<'a>(&'a self, module: &'a wgpu::ShaderModule) -> wgpu::VertexState<'a> {
735 wgpu::VertexState {
736 module,
737 entry_point: Some(&self.name),
738 buffers: &[],
739 compilation_options: Default::default(),
740 }
741 }
742
743 pub fn fragment_state<'a>(
745 &'a self,
746 module: &'a wgpu::ShaderModule,
747 targets: &'a [Option<wgpu::ColorTargetState>],
748 ) -> wgpu::FragmentState<'a> {
749 wgpu::FragmentState {
750 module,
751 entry_point: Some(&self.name),
752 targets,
753 compilation_options: Default::default(),
754 }
755 }
756}
757
758#[derive(Clone, Debug)]
760pub struct ComputeEntryInfo {
761 pub name: Cow<'static, str>,
764 pub workgroup_size: (u32, u32, u32),
766 pub label: String,
768}
769
770#[derive(Clone, Debug)]
786pub struct BufferDescriptorInfo {
787 pub binding_name: String,
789 pub group: u32,
791 pub binding: u32,
793 pub kind: BufferKind,
795 pub size: u64,
797 pub usage: wgpu::BufferUsages,
801 pub ty: crate::ir::Type,
806}
807
808impl BufferDescriptorInfo {
809 pub fn descriptor(&self) -> wgpu::BufferDescriptor<'_> {
814 wgpu::BufferDescriptor {
815 label: Some(&self.binding_name),
816 size: self.size,
817 usage: self.usage,
818 mapped_at_creation: false,
819 }
820 }
821
822 pub fn create_buffer(&self, device: &wgpu::Device) -> wgpu::Buffer {
826 device.create_buffer(&self.descriptor())
827 }
828}
829
830#[derive(Clone, Copy, Debug, PartialEq, Eq)]
832pub enum BufferKind {
833 Uniform,
835 Storage { read_only: bool },
837}
838
839fn stage_of(fn_attrs: &ir::FnAttrs) -> Option<wgpu::ShaderStages> {
841 match fn_attrs {
842 ir::FnAttrs::Vertex => Some(wgpu::ShaderStages::VERTEX),
843 ir::FnAttrs::Fragment => Some(wgpu::ShaderStages::FRAGMENT),
844 ir::FnAttrs::Compute { .. } => Some(wgpu::ShaderStages::COMPUTE),
845 ir::FnAttrs::None => None,
846 }
847}
848
849#[derive(Default)]
853struct FnRefs {
854 idents: std::collections::HashSet<String>,
856 callees: std::collections::HashSet<String>,
859}
860
861fn record_fn_idents(
865 f: &ir::ItemFn,
866 name: String,
867 fn_names: &mut std::collections::HashSet<String>,
868 fn_refs: &mut std::collections::HashMap<String, FnRefs>,
869) {
870 let mut refs = FnRefs::default();
871 collect_refs_in_block(&mut refs, &f.block);
872 fn_names.insert(name.clone());
873 fn_refs.insert(name, refs);
874}
875
876pub fn analyze_ir_module(mut ir_module: wgsl_rs_ir::Module) -> WgpuLinkage {
893 let module_label = ir_module.name;
894 ir::deshadow_module(&mut ir_module);
895 ir::suffix_module(&mut ir_module);
896 let mut linkage = WgpuLinkage {
897 module_label,
898 ir: ir_module,
899 bind_groups: BTreeMap::new(),
900 bind_group_layouts: HashMap::new(),
901 pipeline_layout: None,
902 vertex_entries: Vec::new(),
903 fragment_entries: Vec::new(),
904 compute_entries: Vec::new(),
905 buffers: Vec::new(),
906 };
907
908 let mut binding_names: std::collections::HashSet<String> = std::collections::HashSet::new();
916 let mut fn_names: std::collections::HashSet<String> = std::collections::HashSet::new();
919 let mut fn_refs: std::collections::HashMap<String, FnRefs> = std::collections::HashMap::new();
923 let mut entry_points: Vec<(String, wgpu::ShaderStages)> = Vec::new();
924
925 for item in &linkage.ir.items {
926 match item {
927 ir::Item::Uniform(u) => {
928 binding_names.insert(u.name.clone());
929 }
930 ir::Item::Storage(s) => {
931 binding_names.insert(s.name.clone());
932 }
933 ir::Item::Sampler(s) => {
934 binding_names.insert(s.name.clone());
935 }
936 ir::Item::Texture(t) => {
937 binding_names.insert(t.name.clone());
938 }
939 ir::Item::Fn(f) => {
940 record_fn_idents(f, f.name.to_string(), &mut fn_names, &mut fn_refs);
941 if let Some(stage) = stage_of(&f.fn_attrs) {
942 entry_points.push((f.name.to_string(), stage));
943 }
944 }
945 ir::Item::Impl(imp) => {
946 for impl_item in &imp.items {
947 let ir::ImplItem::Fn(f) = impl_item else {
948 continue;
949 };
950 let mangled = ir::mangle::mangle(&[&imp.self_ty, &f.name]);
954 record_fn_idents(f, mangled.clone(), &mut fn_names, &mut fn_refs);
955 if let Some(stage) = stage_of(&f.fn_attrs) {
956 entry_points.push((mangled, stage));
957 }
958 }
959 }
960 _ => {}
961 }
962 }
963
964 let mut referenced: std::collections::HashMap<String, wgpu::ShaderStages> =
970 std::collections::HashMap::new();
971 for (entry_name, stage) in &entry_points {
972 let mut visited: std::collections::HashSet<&String> = std::collections::HashSet::new();
973 let mut stack: Vec<&String> = vec![entry_name];
974 while let Some(fname) = stack.pop() {
975 if !visited.insert(fname) {
976 continue;
977 }
978 let Some(refs) = fn_refs.get(fname) else {
979 continue;
980 };
981 for name in &refs.idents {
985 if binding_names.contains(name) {
986 referenced
987 .entry(name.clone())
988 .and_modify(|s| *s |= *stage)
989 .or_insert(*stage);
990 }
991 }
992 for callee in &refs.callees {
993 if fn_names.contains(callee) {
994 stack.push(callee);
995 }
996 }
997 }
998 }
999
1000 for item in &linkage.ir.items {
1001 match item {
1002 ir::Item::Uniform(u) => {
1003 let size = type_byte_size_or_zero(&u.ty, &linkage.ir);
1004 let entry = wgpu::BindGroupLayoutEntry {
1005 binding: u.binding,
1006 visibility: visibility_for(&referenced, &u.name),
1007 ty: wgpu::BindingType::Buffer {
1008 ty: wgpu::BufferBindingType::Uniform,
1009 has_dynamic_offset: false,
1010 min_binding_size: None,
1011 },
1012 count: None,
1013 };
1014 linkage
1015 .bind_groups
1016 .entry(u.group)
1017 .or_insert_with(|| BindGroupInfo {
1018 group: u.group,
1019 entries: Vec::new(),
1020 bindings: Vec::new(),
1021 label: format!("{}::bind_group_{}", linkage.module_label, u.group),
1022 })
1023 .entries
1024 .push(entry);
1025 linkage
1026 .bind_groups
1027 .get_mut(&u.group)
1028 .unwrap()
1029 .bindings
1030 .push(BindingMeta {
1031 name: u.name.clone(),
1032 binding: u.binding,
1033 kind: BindingKind::Uniform,
1034 });
1035 linkage.buffers.push(BufferDescriptorInfo {
1036 binding_name: u.name.clone(),
1037 group: u.group,
1038 binding: u.binding,
1039 kind: BufferKind::Uniform,
1040 size,
1041 usage: wgpu::BufferUsages::UNIFORM
1042 | wgpu::BufferUsages::COPY_DST
1043 | wgpu::BufferUsages::COPY_SRC,
1044 ty: u.ty.clone(),
1045 });
1046 }
1047 ir::Item::Storage(s) => {
1048 let read_only = matches!(s.access, ir::StorageAccess::Read);
1049 let size = type_byte_size_or_zero(&s.ty, &linkage.ir);
1050 let entry = wgpu::BindGroupLayoutEntry {
1051 binding: s.binding,
1052 visibility: visibility_for(&referenced, &s.name),
1053 ty: wgpu::BindingType::Buffer {
1054 ty: wgpu::BufferBindingType::Storage { read_only },
1055 has_dynamic_offset: false,
1056 min_binding_size: None,
1057 },
1058 count: None,
1059 };
1060 linkage
1061 .bind_groups
1062 .entry(s.group)
1063 .or_insert_with(|| BindGroupInfo {
1064 group: s.group,
1065 entries: Vec::new(),
1066 bindings: Vec::new(),
1067 label: format!("{}::bind_group_{}", linkage.module_label, s.group),
1068 })
1069 .entries
1070 .push(entry);
1071 linkage
1072 .bind_groups
1073 .get_mut(&s.group)
1074 .unwrap()
1075 .bindings
1076 .push(BindingMeta {
1077 name: s.name.clone(),
1078 binding: s.binding,
1079 kind: BindingKind::Storage { read_only },
1080 });
1081 linkage.buffers.push(BufferDescriptorInfo {
1082 binding_name: s.name.clone(),
1083 group: s.group,
1084 binding: s.binding,
1085 kind: BufferKind::Storage { read_only },
1086 size,
1087 usage: wgpu::BufferUsages::STORAGE
1088 | wgpu::BufferUsages::COPY_DST
1089 | wgpu::BufferUsages::COPY_SRC,
1090 ty: s.ty.clone(),
1091 });
1092 }
1093 ir::Item::Sampler(s) => {
1094 let comparison = matches!(s.ty, ir::Type::SamplerComparison);
1095 let sampler_ty = if comparison {
1096 wgpu::SamplerBindingType::Comparison
1097 } else if !s.filterable {
1098 wgpu::SamplerBindingType::NonFiltering
1099 } else {
1100 wgpu::SamplerBindingType::Filtering
1101 };
1102 let entry = wgpu::BindGroupLayoutEntry {
1103 binding: s.binding,
1104 visibility: visibility_for(&referenced, &s.name),
1105 ty: wgpu::BindingType::Sampler(sampler_ty),
1106 count: None,
1107 };
1108 linkage
1109 .bind_groups
1110 .entry(s.group)
1111 .or_insert_with(|| BindGroupInfo {
1112 group: s.group,
1113 entries: Vec::new(),
1114 bindings: Vec::new(),
1115 label: format!("{}::bind_group_{}", linkage.module_label, s.group),
1116 })
1117 .entries
1118 .push(entry);
1119 linkage
1120 .bind_groups
1121 .get_mut(&s.group)
1122 .unwrap()
1123 .bindings
1124 .push(BindingMeta {
1125 name: s.name.clone(),
1126 binding: s.binding,
1127 kind: BindingKind::Sampler { comparison },
1128 });
1129 }
1130 ir::Item::Texture(t) => {
1131 let (mut entry, kind) = texture_layout_entry(t);
1132 entry.visibility = visibility_for(&referenced, &t.name);
1133 linkage
1134 .bind_groups
1135 .entry(t.group)
1136 .or_insert_with(|| BindGroupInfo {
1137 group: t.group,
1138 entries: Vec::new(),
1139 bindings: Vec::new(),
1140 label: format!("{}::bind_group_{}", linkage.module_label, t.group),
1141 })
1142 .entries
1143 .push(entry);
1144 linkage
1145 .bind_groups
1146 .get_mut(&t.group)
1147 .unwrap()
1148 .bindings
1149 .push(BindingMeta {
1150 name: t.name.clone(),
1151 binding: t.binding,
1152 kind,
1153 });
1154 }
1155 ir::Item::Fn(f) => match &f.fn_attrs {
1156 ir::FnAttrs::Vertex => {
1157 linkage.vertex_entries.push(EntryPointInfo {
1158 name: f.name.clone(),
1159 });
1160 }
1161 ir::FnAttrs::Fragment => {
1162 linkage.fragment_entries.push(EntryPointInfo {
1163 name: f.name.clone(),
1164 });
1165 }
1166 ir::FnAttrs::Compute { workgroup_size } => {
1167 let x = workgroup_size.x;
1168 let y = workgroup_size.y.unwrap_or(1);
1169 let z = workgroup_size.z.unwrap_or(1);
1170 linkage.compute_entries.push(ComputeEntryInfo {
1171 name: f.name.clone(),
1172 workgroup_size: (x, y, z),
1173 label: format!("{}::{}", linkage.module_label, f.name),
1174 });
1175 }
1176 ir::FnAttrs::None => {}
1177 },
1178 ir::Item::Struct(_)
1181 | ir::Item::Impl(_)
1182 | ir::Item::Enum(_)
1183 | ir::Item::Const(_)
1184 | ir::Item::Workgroup(_) => {}
1185 }
1186 }
1187
1188 for bg in linkage.bind_groups.values_mut() {
1191 let mut paired: Vec<_> = bg.entries.drain(..).zip(bg.bindings.drain(..)).collect();
1192 paired.sort_by_key(|(e, _m)| e.binding);
1193 bg.entries = paired.iter().map(|(e, _)| *e).collect();
1194 bg.bindings = paired.into_iter().map(|(_, m)| m).collect();
1195 }
1196
1197 linkage
1198}
1199
1200fn visibility_for(
1209 referenced: &std::collections::HashMap<String, wgpu::ShaderStages>,
1210 name: &str,
1211) -> wgpu::ShaderStages {
1212 referenced
1213 .get(name)
1214 .copied()
1215 .unwrap_or(wgpu::ShaderStages::COMPUTE)
1216}
1217
1218fn collect_refs_in_block(refs: &mut FnRefs, block: &ir::Block) {
1221 for stmt in &block.stmts {
1222 collect_refs_in_stmt(refs, stmt);
1223 }
1224}
1225
1226fn collect_refs_in_stmt(refs: &mut FnRefs, stmt: &ir::Stmt) {
1227 match stmt {
1228 ir::Stmt::Local(l) => {
1229 if let Some(init) = &l.init {
1230 collect_refs_in_expr(refs, init);
1231 }
1232 }
1233 ir::Stmt::Const(c) => {
1234 collect_refs_in_expr(refs, &c.expr);
1235 }
1236 ir::Stmt::Assignment { lhs, rhs } => {
1237 collect_refs_in_expr(refs, lhs);
1238 collect_refs_in_expr(refs, rhs);
1239 }
1240 ir::Stmt::CompoundAssignment { lhs, rhs, .. } => {
1241 collect_refs_in_expr(refs, lhs);
1242 collect_refs_in_expr(refs, rhs);
1243 }
1244 ir::Stmt::While { condition, body } => {
1245 collect_refs_in_expr(refs, condition);
1246 collect_refs_in_block(refs, body);
1247 }
1248 ir::Stmt::Loop { body } => {
1249 collect_refs_in_block(refs, body);
1250 }
1251 ir::Stmt::Expr { expr, .. } => {
1252 collect_refs_in_expr(refs, expr);
1253 }
1254 ir::Stmt::If(s) => {
1255 collect_refs_in_expr(refs, &s.condition);
1256 collect_refs_in_block(refs, &s.then_block);
1257 if let Some(else_branch) = &s.else_branch {
1258 match else_branch {
1259 ir::ElseBranch::Block(b) => collect_refs_in_block(refs, b),
1260 ir::ElseBranch::If(i) => {
1261 collect_refs_in_expr(refs, &i.condition);
1262 collect_refs_in_block(refs, &i.then_block);
1263 if let Some(else_branch) = &i.else_branch {
1264 match else_branch {
1265 ir::ElseBranch::Block(b) => collect_refs_in_block(refs, b),
1266 ir::ElseBranch::If(_) => {
1267 }
1270 }
1271 }
1272 }
1273 }
1274 }
1275 }
1276 ir::Stmt::Return(Some(e)) => collect_refs_in_expr(refs, e),
1277 ir::Stmt::For(f) => {
1278 collect_refs_in_expr(refs, &f.from);
1279 collect_refs_in_expr(refs, &f.to);
1280 collect_refs_in_block(refs, &f.body);
1281 }
1282 ir::Stmt::Switch(s) => {
1283 collect_refs_in_expr(refs, &s.selector);
1284 for arm in &s.arms {
1285 for sel in &arm.selectors {
1286 match sel {
1287 ir::CaseSelector::Literal(_) => {}
1288 ir::CaseSelector::Expr(e) => collect_refs_in_expr(refs, e),
1289 ir::CaseSelector::Default => {}
1290 }
1291 }
1292 collect_refs_in_block(refs, &arm.body);
1293 }
1294 }
1295 ir::Stmt::Block(b) => collect_refs_in_block(refs, b),
1296 ir::Stmt::SlabCopy {
1297 src,
1298 src_offset,
1299 dest,
1300 dest_offset,
1301 size,
1302 } => {
1303 collect_refs_in_expr(refs, src);
1304 collect_refs_in_expr(refs, src_offset);
1305 collect_refs_in_expr(refs, dest);
1306 collect_refs_in_expr(refs, dest_offset);
1307 collect_refs_in_expr(refs, size);
1308 }
1309 ir::Stmt::Break
1310 | ir::Stmt::Continue
1311 | ir::Stmt::Discard
1312 | ir::Stmt::Return(None)
1313 | ir::Stmt::Macro { .. } => {}
1314 }
1315}
1316
1317fn collect_refs_in_expr(refs: &mut FnRefs, expr: &ir::Expr) {
1318 match expr {
1319 ir::Expr::Ident(name) => {
1320 refs.idents.insert(name.clone());
1321 }
1322 ir::Expr::Lit(_) => {}
1323 ir::Expr::Array { elems } => {
1324 for e in elems {
1325 collect_refs_in_expr(refs, e);
1326 }
1327 }
1328 ir::Expr::Paren(e) => collect_refs_in_expr(refs, e),
1329 ir::Expr::Binary { lhs, rhs, .. } => {
1330 collect_refs_in_expr(refs, lhs);
1331 collect_refs_in_expr(refs, rhs);
1332 }
1333 ir::Expr::Unary { expr, .. } => collect_refs_in_expr(refs, expr),
1334 ir::Expr::ArrayIndexing { lhs, index } => {
1335 collect_refs_in_expr(refs, lhs);
1336 collect_refs_in_expr(refs, index);
1337 }
1338 ir::Expr::Swizzle { lhs, params, .. } => {
1339 collect_refs_in_expr(refs, lhs);
1340 if let Some(args) = params {
1341 for a in args {
1342 collect_refs_in_expr(refs, a);
1343 }
1344 }
1345 }
1346 ir::Expr::Cast { lhs, .. } => collect_refs_in_expr(refs, lhs),
1347 ir::Expr::FnCall { path, params, .. } => {
1348 match path {
1354 ir::FnPath::Ident(name) => {
1355 refs.callees.insert(name.clone());
1356 }
1357 ir::FnPath::TypeMethod { ty, method } => {
1358 refs.callees.insert(ir::mangle::mangle(&[ty, method]));
1359 }
1360 }
1361 for p in params {
1362 collect_refs_in_expr(refs, p);
1363 }
1364 }
1365 ir::Expr::Struct { fields, .. } => {
1366 for fv in fields {
1367 collect_refs_in_expr(refs, &fv.expr);
1368 }
1369 }
1370 ir::Expr::FieldAccess { base, .. } => collect_refs_in_expr(refs, base),
1371 ir::Expr::TypePath { .. } => {}
1372 ir::Expr::Reference(e) => collect_refs_in_expr(refs, e),
1373 ir::Expr::ZeroValueArray { len, .. } => collect_refs_in_expr(refs, len),
1374 }
1375}
1376
1377pub fn analyze_wgsl_module(wgsl_source: &Source) -> Result<WgpuLinkage, Error> {
1393 if wgsl_source.is_template() {
1394 return Err(crate::SourceError::TemplateWgsl {
1395 uninstantiated_source: wgsl_source,
1396 }
1397 .into());
1398 }
1399 let ir_module = assemble_ir(wgsl_source)?;
1400 Ok(analyze_ir_module(ir_module))
1401}
1402
1403pub(crate) fn assemble_ir(wgsl_source: &Source) -> Result<ir::Module, Error> {
1415 let mut visited: std::collections::HashSet<u64> = std::collections::HashSet::new();
1416 let mut seen: std::collections::HashSet<(u64, String, Vec<String>)> =
1417 std::collections::HashSet::new();
1418 let mut items: Vec<ir::Item> = Vec::new();
1419 collect_items(wgsl_source, &mut visited, &mut seen, &mut items, None)?;
1420 Ok(ir::Module {
1424 name: wgsl_source.name,
1425 items,
1426 attrs: Vec::new(),
1427 })
1428}
1429
1430fn collect_items(
1431 wgsl_source: &Source,
1432 visited: &mut std::collections::HashSet<u64>,
1433 seen: &mut std::collections::HashSet<(u64, String, Vec<String>)>,
1434 out: &mut Vec<ir::Item>,
1435 subst: Option<&std::collections::HashMap<String, ir::Type>>,
1436) -> Result<(), Error> {
1437 for m in wgsl_source.imports {
1439 if visited.insert(m.id) {
1440 collect_items(m, visited, seen, out, None)?;
1441 }
1442 }
1443
1444 let mut ir_module = (wgsl_source.ir_constructor)();
1446 if let Some(s) = subst {
1447 ir::substitute_types(&mut ir_module, s);
1448 }
1449 out.extend(ir_module.items);
1450
1451 for inst in wgsl_source.instantiations {
1453 let mangled: Vec<String> = inst
1454 .mangled_type_args
1455 .iter()
1456 .map(|s| (*s).to_string())
1457 .collect();
1458 let type_args = (inst.type_args_constructor)();
1459 collect_instantiation(
1460 inst.modules,
1461 inst.template_name,
1462 &mangled,
1463 &type_args,
1464 out,
1465 seen,
1466 )?;
1467 }
1468 Ok(())
1469}
1470
1471fn collect_instantiation(
1472 sources: &[&Source],
1473 template_name: &str,
1474 mangled_type_args: &[String],
1475 type_args: &[ir::Type],
1476 out: &mut Vec<ir::Item>,
1477 seen: &mut std::collections::HashSet<(u64, String, Vec<String>)>,
1478) -> Result<(), Error> {
1479 let available: Vec<String> = sources
1480 .iter()
1481 .copied()
1482 .flat_map(|m| m.templates.iter().map(|t| t.name.to_string()))
1483 .collect();
1484
1485 let mut matching: Vec<&Source> = sources
1486 .iter()
1487 .copied()
1488 .filter(|m| m.templates.iter().any(|t| t.name == template_name))
1489 .collect();
1490
1491 snafu::ensure!(
1492 !matching.is_empty(),
1493 TemplateResolutionSnafu {
1494 template_name: template_name.to_string(),
1495 mangled_type_args: mangled_type_args.to_vec(),
1496 available,
1497 }
1498 );
1499
1500 if matching.len() > 1 {
1501 let names: Vec<String> = matching.iter().map(|m| m.name.to_string()).collect();
1502 return TemplateResolutionSnafu {
1503 template_name: template_name.to_string(),
1504 mangled_type_args: mangled_type_args.to_vec(),
1505 available: names,
1506 }
1507 .fail();
1508 }
1509
1510 let source = matching
1511 .pop()
1512 .expect("matching is non-empty after the check above");
1513 let Some(template) = source.templates.iter().find(|t| t.name == template_name) else {
1514 unreachable!(
1518 "resolved source '{}' has no template '{}'; available: {:?}",
1519 source.name, template_name, available
1520 );
1521 };
1522
1523 let key = (
1524 source.id,
1525 template_name.to_string(),
1526 mangled_type_args.to_vec(),
1527 );
1528 snafu::ensure!(
1529 seen.insert(key),
1530 TemplateResolutionSnafu {
1531 template_name: template_name.to_owned(),
1532 mangled_type_args: mangled_type_args.to_vec(),
1533 available
1534 }
1535 );
1536
1537 for dep in template.dependencies {
1539 let dep_mangled: Vec<String> = dep
1540 .type_param_mapping
1541 .iter()
1542 .map(|&idx| mangled_type_args[idx].clone())
1543 .collect();
1544 let dep_args: Vec<ir::Type> = dep
1545 .type_param_mapping
1546 .iter()
1547 .map(|&idx| type_args[idx].clone())
1548 .collect();
1549 collect_instantiation(&[source], dep.callee, &dep_mangled, &dep_args, out, seen)?;
1550 }
1551
1552 let mut subst: std::collections::HashMap<String, ir::Type> = std::collections::HashMap::new();
1553 for (param, arg) in template.type_params.iter().zip(type_args.iter()) {
1554 subst.insert((*param).to_string(), arg.clone());
1555 }
1556
1557 let mut items = (template.ir_constructor)();
1558 ir::substitute_items(&mut items, &subst);
1559
1560 let instance_name = if mangled_type_args.is_empty() {
1561 template.name.to_string()
1562 } else {
1563 let mut components: Vec<&str> = Vec::with_capacity(1 + mangled_type_args.len());
1564 components.push(template.name);
1565 for s in mangled_type_args {
1566 components.push(s.as_str());
1567 }
1568 ir::mangle(&components)
1569 };
1570 if instance_name != template.name {
1571 ir::rename_items(&mut items, template.name, &instance_name);
1572 }
1573 out.extend(items);
1574
1575 Ok(())
1576}
1577
1578fn texture_layout_entry(item: &ir::ItemTexture) -> (wgpu::BindGroupLayoutEntry, BindingKind) {
1581 let view_dimension = match &item.ty {
1582 ir::Type::Texture { kind, .. } => texture_kind_view_dimension(*kind),
1583 ir::Type::TextureDepth { kind } => texture_depth_view_dimension(*kind),
1584 ir::Type::TextureStorage { kind, .. } => texture_storage_kind_view_dimension(*kind),
1585 _ => unreachable!("ItemTexture validated to be a texture type"),
1586 };
1587 let multisampled = match &item.ty {
1588 ir::Type::Texture { kind, .. } => matches!(kind, ir::TextureKind::TextureMultisampled2D),
1589 ir::Type::TextureDepth { kind } => {
1590 matches!(kind, ir::TextureDepthKind::DepthMultisampled2D)
1591 }
1592 ir::Type::TextureStorage { .. } => false,
1593 _ => unreachable!(),
1594 };
1595 match &item.ty {
1596 ir::Type::Texture { sampled_type, .. } => {
1597 let sample = match sampled_type {
1598 ir::ScalarType::F32 => wgpu::TextureSampleType::Float {
1599 filterable: item.filterable,
1600 },
1601 ir::ScalarType::I32 => wgpu::TextureSampleType::Sint,
1602 ir::ScalarType::U32 => wgpu::TextureSampleType::Uint,
1603 ir::ScalarType::Bool => unreachable!("textures can't be bool-sampled"),
1604 };
1605 (
1606 wgpu::BindGroupLayoutEntry {
1607 binding: item.binding,
1608 visibility: wgpu::ShaderStages::all(),
1609 ty: wgpu::BindingType::Texture {
1610 sample_type: sample,
1611 view_dimension,
1612 multisampled,
1613 },
1614 count: None,
1615 },
1616 BindingKind::Texture,
1617 )
1618 }
1619 ir::Type::TextureDepth { .. } => (
1620 wgpu::BindGroupLayoutEntry {
1621 binding: item.binding,
1622 visibility: wgpu::ShaderStages::all(),
1623 ty: wgpu::BindingType::Texture {
1624 sample_type: wgpu::TextureSampleType::Depth,
1625 view_dimension,
1626 multisampled,
1627 },
1628 count: None,
1629 },
1630 BindingKind::DepthTexture,
1631 ),
1632 ir::Type::TextureStorage { format, access, .. } => {
1633 let wgpu_format = texel_format_to_wgpu(*format);
1634 let wgpu_access = match access {
1635 ir::StorageTextureAccess::Read => wgpu::StorageTextureAccess::ReadOnly,
1636 ir::StorageTextureAccess::Write => wgpu::StorageTextureAccess::WriteOnly,
1637 ir::StorageTextureAccess::ReadWrite => wgpu::StorageTextureAccess::ReadWrite,
1638 };
1639 let read_write = matches!(access, ir::StorageTextureAccess::ReadWrite);
1640 (
1641 wgpu::BindGroupLayoutEntry {
1642 binding: item.binding,
1643 visibility: wgpu::ShaderStages::all(),
1644 ty: wgpu::BindingType::StorageTexture {
1645 access: wgpu_access,
1646 format: wgpu_format,
1647 view_dimension,
1648 },
1649 count: None,
1650 },
1651 BindingKind::StorageTexture { read_write },
1652 )
1653 }
1654 _ => unreachable!(),
1655 }
1656}
1657
1658fn texture_storage_kind_view_dimension(kind: ir::TextureStorageKind) -> wgpu::TextureViewDimension {
1659 match kind {
1660 ir::TextureStorageKind::Storage1D => wgpu::TextureViewDimension::D1,
1661 ir::TextureStorageKind::Storage2D => wgpu::TextureViewDimension::D2,
1662 ir::TextureStorageKind::Storage2DArray => wgpu::TextureViewDimension::D2Array,
1663 ir::TextureStorageKind::Storage3D => wgpu::TextureViewDimension::D3,
1664 }
1665}
1666
1667fn texel_format_to_wgpu(f: ir::TexelFormat) -> wgpu::TextureFormat {
1669 use ir::TexelFormat::*;
1670 match f {
1671 Rgba8unorm => wgpu::TextureFormat::Rgba8Unorm,
1672 Rgba8snorm => wgpu::TextureFormat::Rgba8Snorm,
1673 Rgba8uint => wgpu::TextureFormat::Rgba8Uint,
1674 Rgba8sint => wgpu::TextureFormat::Rgba8Sint,
1675 Rgba16uint => wgpu::TextureFormat::Rgba16Uint,
1676 Rgba16sint => wgpu::TextureFormat::Rgba16Sint,
1677 Rgba16float => wgpu::TextureFormat::Rgba16Float,
1678 R32uint => wgpu::TextureFormat::R32Uint,
1679 R32sint => wgpu::TextureFormat::R32Sint,
1680 R32float => wgpu::TextureFormat::R32Float,
1681 Rg32uint => wgpu::TextureFormat::Rg32Uint,
1682 Rg32sint => wgpu::TextureFormat::Rg32Sint,
1683 Rg32float => wgpu::TextureFormat::Rg32Float,
1684 Rgba32uint => wgpu::TextureFormat::Rgba32Uint,
1685 Rgba32sint => wgpu::TextureFormat::Rgba32Sint,
1686 Rgba32float => wgpu::TextureFormat::Rgba32Float,
1687 Bgra8unorm => wgpu::TextureFormat::Bgra8Unorm,
1688 Rgba16unorm => wgpu::TextureFormat::Rgba16Unorm,
1689 Rgba16snorm => wgpu::TextureFormat::Rgba16Snorm,
1690 Rg8unorm => wgpu::TextureFormat::Rg8Unorm,
1691 Rg8snorm => wgpu::TextureFormat::Rg8Snorm,
1692 Rg8uint => wgpu::TextureFormat::Rg8Uint,
1693 Rg8sint => wgpu::TextureFormat::Rg8Sint,
1694 Rg16unorm => wgpu::TextureFormat::Rg16Unorm,
1695 Rg16snorm => wgpu::TextureFormat::Rg16Snorm,
1696 Rg16uint => wgpu::TextureFormat::Rg16Uint,
1697 Rg16sint => wgpu::TextureFormat::Rg16Sint,
1698 Rg16float => wgpu::TextureFormat::Rg16Float,
1699 R8unorm => wgpu::TextureFormat::R8Unorm,
1700 R8snorm => wgpu::TextureFormat::R8Snorm,
1701 R8uint => wgpu::TextureFormat::R8Uint,
1702 R8sint => wgpu::TextureFormat::R8Sint,
1703 R16unorm => wgpu::TextureFormat::R16Unorm,
1704 R16snorm => wgpu::TextureFormat::R16Snorm,
1705 R16uint => wgpu::TextureFormat::R16Uint,
1706 R16sint => wgpu::TextureFormat::R16Sint,
1707 R16float => wgpu::TextureFormat::R16Float,
1708 Rgb10a2unorm => wgpu::TextureFormat::Rgb10a2Unorm,
1709 Rgb10a2uint => wgpu::TextureFormat::Rgb10a2Uint,
1710 Rg11b10ufloat => wgpu::TextureFormat::Rg11b10Ufloat,
1711 }
1712}
1713
1714fn texture_kind_view_dimension(kind: ir::TextureKind) -> wgpu::TextureViewDimension {
1715 match kind {
1716 ir::TextureKind::Texture1D => wgpu::TextureViewDimension::D1,
1717 ir::TextureKind::Texture2D | ir::TextureKind::TextureMultisampled2D => {
1718 wgpu::TextureViewDimension::D2
1719 }
1720 ir::TextureKind::Texture2DArray => wgpu::TextureViewDimension::D2Array,
1721 ir::TextureKind::Texture3D => wgpu::TextureViewDimension::D3,
1722 ir::TextureKind::TextureCube => wgpu::TextureViewDimension::Cube,
1723 ir::TextureKind::TextureCubeArray => wgpu::TextureViewDimension::CubeArray,
1724 }
1725}
1726
1727fn texture_depth_view_dimension(kind: ir::TextureDepthKind) -> wgpu::TextureViewDimension {
1728 match kind {
1729 ir::TextureDepthKind::Depth2D | ir::TextureDepthKind::DepthMultisampled2D => {
1730 wgpu::TextureViewDimension::D2
1731 }
1732 ir::TextureDepthKind::Depth2DArray => wgpu::TextureViewDimension::D2Array,
1733 ir::TextureDepthKind::DepthCube => wgpu::TextureViewDimension::Cube,
1734 ir::TextureDepthKind::DepthCubeArray => wgpu::TextureViewDimension::CubeArray,
1735 }
1736}
1737
1738pub fn type_byte_size(ty: &ir::Type, module: &ir::Module) -> u64 {
1750 let layout = type_layout(ty, module);
1751 layout.size
1752}
1753
1754fn type_byte_size_or_zero(ty: &ir::Type, module: &ir::Module) -> u64 {
1755 type_byte_size(ty, module)
1756}
1757
1758#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1760pub struct TypeLayout {
1761 pub size: u64,
1762 pub align: u64,
1763}
1764
1765fn align_up(val: u64, align: u64) -> u64 {
1766 if align == 0 {
1767 val
1768 } else {
1769 (val + (align - 1)) & !(align - 1)
1770 }
1771}
1772
1773fn type_layout(ty: &ir::Type, module: &ir::Module) -> TypeLayout {
1774 match ty {
1775 ir::Type::Scalar(s) => scalar_layout(*s),
1776 ir::Type::Vector { elements, .. } => vector_layout(*elements),
1777 ir::Type::Matrix { columns, rows, .. } => matrix_layout(*columns, *rows),
1778 ir::Type::Array { elem, len } => {
1779 let inner = type_layout(elem, module);
1780 let n = eval_array_len(len, module).unwrap_or(0);
1781 TypeLayout {
1782 size: (n as u64) * align_up(inner.size, inner.align.max(1)),
1783 align: inner.align,
1784 }
1785 }
1786 ir::Type::RuntimeArray { elem } => {
1787 let inner = type_layout(elem, module);
1788 TypeLayout {
1789 size: 0,
1790 align: inner.align,
1791 }
1792 }
1793 ir::Type::Atomic { elem } => type_layout(elem, module),
1794 ir::Type::Struct { name, type_args } => struct_layout(name, type_args, module),
1795 ir::Type::Ptr { .. } => TypeLayout { size: 0, align: 1 },
1796 ir::Type::Sampler
1799 | ir::Type::SamplerComparison
1800 | ir::Type::Texture { .. }
1801 | ir::Type::TextureDepth { .. }
1802 | ir::Type::TextureStorage { .. }
1803 | ir::Type::TypeParam { .. }
1804 | ir::Type::Phantom { .. }
1805 | ir::Type::AssocType { .. } => TypeLayout { size: 0, align: 1 },
1806 }
1807}
1808
1809fn scalar_layout(s: ir::ScalarType) -> TypeLayout {
1810 match s {
1811 ir::ScalarType::F32 | ir::ScalarType::I32 | ir::ScalarType::U32 | ir::ScalarType::Bool => {
1812 TypeLayout { size: 4, align: 4 }
1813 }
1814 }
1815}
1816
1817fn vector_layout(elements: u8) -> TypeLayout {
1818 match elements {
1819 2 => TypeLayout { size: 8, align: 8 },
1820 3 => TypeLayout {
1821 size: 12,
1822 align: 16,
1823 },
1824 4 => TypeLayout {
1825 size: 16,
1826 align: 16,
1827 },
1828 _ => TypeLayout { size: 0, align: 1 },
1829 }
1830}
1831
1832fn matrix_layout(columns: u8, rows: u8) -> TypeLayout {
1833 let row_vec = vector_layout(rows);
1839 let stride = align_up(row_vec.size, row_vec.align);
1840 TypeLayout {
1841 size: (columns as u64) * stride,
1842 align: row_vec.align,
1843 }
1844}
1845
1846fn struct_layout(name: &str, type_args: &[ir::Type], module: &ir::Module) -> TypeLayout {
1847 let Some(s) = find_struct(name, type_args, module) else {
1848 return TypeLayout { size: 0, align: 1 };
1849 };
1850 let mut offset: u64 = 0;
1851 let mut struct_align: u64 = 1;
1852 for field in &s.fields {
1853 let fl = type_layout(&field.ty, module);
1854 struct_align = struct_align.max(fl.align);
1855 offset = align_up(offset, fl.align);
1856 offset += fl.size;
1857 }
1858 let size = align_up(offset, struct_align);
1859 TypeLayout {
1860 size,
1861 align: struct_align,
1862 }
1863}
1864
1865fn find_struct<'a>(
1872 name: &str,
1873 _type_args: &[ir::Type],
1874 module: &'a ir::Module,
1875) -> Option<&'a ir::ItemStruct> {
1876 module.items.iter().find_map(|i| match i {
1877 ir::Item::Struct(s) if s.name == name => Some(s),
1878 _ => None,
1879 })
1880}
1881
1882fn eval_array_len(expr: &ir::Expr, module: &ir::Module) -> Option<usize> {
1887 match expr {
1888 ir::Expr::Lit(ir::Lit::Int { digits, .. }) => digits.parse::<usize>().ok(),
1889 ir::Expr::Unary {
1893 op: ir::UnOp::Neg, ..
1894 } => None,
1895 ir::Expr::Ident(name) => {
1896 let val = module.items.iter().find_map(|i| match i {
1897 ir::Item::Const(c) if c.name == *name => Some(eval_const_int(&c.expr, module)),
1898 _ => None,
1899 });
1900 val.flatten()
1901 }
1902 _ => None,
1903 }
1904}
1905
1906fn eval_const_int(expr: &ir::Expr, module: &ir::Module) -> Option<usize> {
1907 eval_array_len(expr, module)
1908}
1909
1910#[cfg(test)]
1911mod binding_visibility {
1912 use std::borrow::Cow;
1916
1917 use super::*;
1918
1919 fn f32_ty() -> ir::Type {
1920 ir::Type::Scalar(ir::ScalarType::F32)
1921 }
1922
1923 fn zero() -> ir::Expr {
1924 ir::Expr::Lit(ir::Lit::Float {
1925 text: "0.0".to_string(),
1926 })
1927 }
1928
1929 fn uniform(name: &str, group: u32, binding: u32) -> ir::Item {
1930 ir::Item::Uniform(ir::ItemUniform {
1931 group,
1932 binding,
1933 name: name.to_string(),
1934 ty: f32_ty(),
1935 attrs: Vec::new(),
1936 })
1937 }
1938
1939 fn call(name: &str, params: Vec<ir::Expr>) -> ir::Expr {
1940 ir::Expr::FnCall {
1941 path: ir::FnPath::Ident(name.to_string()),
1942 type_args: Vec::new(),
1943 params,
1944 }
1945 }
1946
1947 fn ident(name: &str) -> ir::Expr {
1948 ir::Expr::Ident(name.to_string())
1949 }
1950
1951 fn fn_item(name: &str, fn_attrs: ir::FnAttrs, stmts: Vec<ir::Stmt>) -> ir::Item {
1952 ir::Item::Fn(ir::ItemFn {
1953 type_params: Vec::new(),
1954 const_params: Vec::new(),
1955 fn_attrs,
1956 name: Cow::Owned(name.to_string()),
1957 inputs: Vec::new(),
1958 return_type: ir::ReturnType::Default,
1959 block: ir::Block { stmts },
1960 attrs: Vec::new(),
1961 })
1962 }
1963
1964 fn returns(expr: ir::Expr) -> ir::Stmt {
1965 ir::Stmt::Return(Some(expr))
1966 }
1967
1968 fn analyze(items: Vec<ir::Item>) -> WgpuLinkage {
1969 analyze_ir_module(ir::Module {
1970 name: "test_module",
1971 items,
1972 attrs: Vec::new(),
1973 })
1974 }
1975
1976 fn visibility_of(linkage: &WgpuLinkage, name: &str) -> wgpu::ShaderStages {
1978 for bind_group in linkage.bind_groups.values() {
1979 if let Some(meta) = bind_group.bindings.iter().find(|b| b.name == name) {
1980 let entry = bind_group
1981 .entries
1982 .iter()
1983 .find(|e| e.binding == meta.binding)
1984 .expect("binding layout entry exists");
1985 return entry.visibility;
1986 }
1987 }
1988 panic!("binding '{name}' not found in any bind group");
1989 }
1990
1991 #[test]
1992 fn direct_use_gets_entry_stage() {
1993 let linkage = analyze(vec![
1994 uniform("MY_UNIFORM", 0, 0),
1995 fn_item(
1996 "vert",
1997 ir::FnAttrs::Vertex,
1998 vec![returns(ident("MY_UNIFORM"))],
1999 ),
2000 ]);
2001 assert_eq!(
2002 visibility_of(&linkage, "MY_UNIFORM"),
2003 wgpu::ShaderStages::VERTEX
2004 );
2005 }
2006
2007 #[test]
2010 fn helper_only_use_carries_entry_point_stages() {
2011 let linkage = analyze(vec![
2012 uniform("MY_UNIFORM", 0, 0),
2013 fn_item(
2014 "read_uniform",
2015 ir::FnAttrs::None,
2016 vec![returns(ident("MY_UNIFORM"))],
2017 ),
2018 fn_item(
2019 "vert",
2020 ir::FnAttrs::Vertex,
2021 vec![returns(call("read_uniform", vec![]))],
2022 ),
2023 fn_item(
2024 "frag",
2025 ir::FnAttrs::Fragment,
2026 vec![returns(call("read_uniform", vec![]))],
2027 ),
2028 ]);
2029 assert_eq!(
2030 visibility_of(&linkage, "MY_UNIFORM"),
2031 wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
2032 );
2033 }
2034
2035 #[test]
2037 fn frag_only_helper_stays_fragment() {
2038 let linkage = analyze(vec![
2039 uniform("MY_UNIFORM", 0, 0),
2040 fn_item(
2041 "read_uniform",
2042 ir::FnAttrs::None,
2043 vec![returns(ident("MY_UNIFORM"))],
2044 ),
2045 fn_item(
2046 "frag",
2047 ir::FnAttrs::Fragment,
2048 vec![returns(call("read_uniform", vec![]))],
2049 ),
2050 fn_item("vert", ir::FnAttrs::Vertex, vec![returns(zero())]),
2051 ]);
2052 assert_eq!(
2053 visibility_of(&linkage, "MY_UNIFORM"),
2054 wgpu::ShaderStages::FRAGMENT
2055 );
2056 }
2057
2058 #[test]
2061 fn mixed_direct_and_helper_use() {
2062 let linkage = analyze(vec![
2063 uniform("MY_UNIFORM", 0, 0),
2064 fn_item(
2065 "read_uniform",
2066 ir::FnAttrs::None,
2067 vec![returns(ident("MY_UNIFORM"))],
2068 ),
2069 fn_item(
2070 "vert",
2071 ir::FnAttrs::Vertex,
2072 vec![returns(ident("MY_UNIFORM"))],
2073 ),
2074 fn_item(
2075 "frag",
2076 ir::FnAttrs::Fragment,
2077 vec![returns(call("read_uniform", vec![]))],
2078 ),
2079 ]);
2080 assert_eq!(
2081 visibility_of(&linkage, "MY_UNIFORM"),
2082 wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT
2083 );
2084 }
2085
2086 #[test]
2089 fn helper_chain_is_transitive() {
2090 let linkage = analyze(vec![
2091 uniform("MY_UNIFORM", 0, 0),
2092 fn_item(
2093 "inner",
2094 ir::FnAttrs::None,
2095 vec![returns(ident("MY_UNIFORM"))],
2096 ),
2097 fn_item(
2098 "outer",
2099 ir::FnAttrs::None,
2100 vec![returns(call("inner", vec![]))],
2101 ),
2102 fn_item(
2103 "vert",
2104 ir::FnAttrs::Vertex,
2105 vec![returns(call("outer", vec![]))],
2106 ),
2107 ]);
2108 assert_eq!(
2109 visibility_of(&linkage, "MY_UNIFORM"),
2110 wgpu::ShaderStages::VERTEX
2111 );
2112 }
2113
2114 #[test]
2117 fn unused_binding_defaults_to_compute() {
2118 let linkage = analyze(vec![
2119 uniform("MY_UNIFORM", 0, 0),
2120 fn_item("vert", ir::FnAttrs::Vertex, vec![returns(zero())]),
2121 ]);
2122 assert_eq!(
2123 visibility_of(&linkage, "MY_UNIFORM"),
2124 wgpu::ShaderStages::COMPUTE
2125 );
2126 }
2127
2128 #[test]
2133 fn local_shadowing_fn_name_is_not_a_call_edge() {
2134 let linkage = analyze(vec![
2135 uniform("DATA", 0, 0),
2136 fn_item(
2137 "read_uniform",
2138 ir::FnAttrs::None,
2139 vec![returns(ident("DATA"))],
2140 ),
2141 fn_item(
2142 "vert",
2143 ir::FnAttrs::Vertex,
2144 vec![
2145 ir::Stmt::Local(ir::Local {
2146 mutable: false,
2147 name: "read_uniform".to_string(),
2148 ty: None,
2149 init: Some(zero()),
2150 }),
2151 returns(ident("read_uniform")),
2152 ],
2153 ),
2154 ]);
2155 assert_eq!(visibility_of(&linkage, "DATA"), wgpu::ShaderStages::COMPUTE);
2158 }
2159
2160 #[test]
2163 fn call_edge_survives_local_shadowing_elsewhere() {
2164 let linkage = analyze(vec![
2165 uniform("DATA", 0, 0),
2166 fn_item(
2167 "read_uniform",
2168 ir::FnAttrs::None,
2169 vec![returns(ident("DATA"))],
2170 ),
2171 fn_item(
2172 "vert",
2173 ir::FnAttrs::Vertex,
2174 vec![
2175 ir::Stmt::Local(ir::Local {
2176 mutable: false,
2177 name: "read_uniform".to_string(),
2178 ty: None,
2179 init: Some(zero()),
2180 }),
2181 returns(ident("read_uniform")),
2182 ],
2183 ),
2184 fn_item(
2185 "frag",
2186 ir::FnAttrs::Fragment,
2187 vec![returns(call("read_uniform", vec![]))],
2188 ),
2189 ]);
2190 assert_eq!(
2191 visibility_of(&linkage, "DATA"),
2192 wgpu::ShaderStages::FRAGMENT
2193 );
2194 }
2195
2196 #[test]
2200 fn impl_method_helper_is_reachable() {
2201 let method = ir::ItemFn {
2202 type_params: Vec::new(),
2203 const_params: Vec::new(),
2204 fn_attrs: ir::FnAttrs::None,
2205 name: Cow::Owned("read_uniform".to_string()),
2206 inputs: Vec::new(),
2207 return_type: ir::ReturnType::Default,
2208 block: ir::Block {
2209 stmts: vec![returns(ident("MY_UNIFORM"))],
2210 },
2211 attrs: Vec::new(),
2212 };
2213 let linkage = analyze(vec![
2214 uniform("MY_UNIFORM", 0, 0),
2215 ir::Item::Impl(ir::ItemImpl {
2216 type_params: Vec::new(),
2217 const_params: Vec::new(),
2218 self_ty: "Foo".to_string(),
2219 items: vec![ir::ImplItem::Fn(method)],
2220 attrs: Vec::new(),
2221 }),
2222 fn_item(
2223 "vert",
2224 ir::FnAttrs::Vertex,
2225 vec![returns(ir::Expr::FnCall {
2226 path: ir::FnPath::TypeMethod {
2227 ty: "Foo".to_string(),
2228 method: "read_uniform".to_string(),
2229 },
2230 type_args: Vec::new(),
2231 params: Vec::new(),
2232 })],
2233 ),
2234 ]);
2235 assert_eq!(
2236 visibility_of(&linkage, "MY_UNIFORM"),
2237 wgpu::ShaderStages::VERTEX
2238 );
2239 }
2240}