1use alloc::{format, string::String, vec::Vec};
2
3use super::{
4 ast::*,
5 context::{Context, ExprPos},
6 error::{Error, ErrorKind},
7 Frontend, Result, Span,
8};
9use crate::{
10 AddressSpace, Binding, BuiltIn, Constant, Expression, GlobalVariable, Handle, Interpolation,
11 LocalVariable, ResourceBinding, Scalar, ScalarKind, ShaderStage, SwizzleComponent, Type,
12 TypeInner, VectorSize,
13};
14
15pub struct VarDeclaration<'a, 'key> {
16 pub qualifiers: &'a mut TypeQualifiers<'key>,
17 pub ty: Handle<Type>,
18 pub name: Option<String>,
19 pub init: Option<Handle<Expression>>,
20 pub meta: Span,
21}
22
23struct BuiltInData {
25 inner: TypeInner,
27 builtin: BuiltIn,
29 mutable: bool,
31 storage: StorageQualifier,
33}
34
35pub enum GlobalOrConstant {
36 Global(Handle<GlobalVariable>),
37 Constant(Handle<Constant>),
38}
39
40impl Frontend {
41 fn add_builtin(
43 &mut self,
44 ctx: &mut Context,
45 name: &str,
46 data: BuiltInData,
47 meta: Span,
48 ) -> Result<Option<VariableReference>> {
49 let ty = ctx.module.types.insert(
50 Type {
51 name: None,
52 inner: data.inner,
53 },
54 meta,
55 );
56
57 let handle = ctx.module.global_variables.append(
58 GlobalVariable {
59 name: Some(name.into()),
60 space: AddressSpace::Private,
61 binding: None,
62 ty,
63 init: None,
64 },
65 meta,
66 );
67
68 let idx = self.entry_args.len();
69 self.entry_args.push(EntryArg {
70 name: Some(name.into()),
71 binding: Binding::BuiltIn(data.builtin),
72 handle,
73 storage: data.storage,
74 });
75
76 self.global_variables.push((
77 name.into(),
78 GlobalLookup {
79 kind: GlobalLookupKind::Variable(handle),
80 entry_arg: Some(idx),
81 mutable: data.mutable,
82 },
83 ));
84
85 let expr = ctx.add_expression(Expression::GlobalVariable(handle), meta)?;
86
87 let var = VariableReference {
88 expr,
89 load: true,
90 mutable: data.mutable,
91 constant: None,
92 entry_arg: Some(idx),
93 };
94
95 ctx.symbol_table.add_root(name.into(), var.clone());
96
97 Ok(Some(var))
98 }
99
100 pub(crate) fn lookup_variable(
101 &mut self,
102 ctx: &mut Context,
103 name: &str,
104 meta: Span,
105 ) -> Result<Option<VariableReference>> {
106 if let Some(var) = ctx.symbol_table.lookup(name).cloned() {
107 return Ok(Some(var));
108 }
109
110 let data = match name {
111 "gl_Position" => BuiltInData {
112 inner: TypeInner::Vector {
113 size: VectorSize::Quad,
114 scalar: Scalar::F32,
115 },
116 builtin: BuiltIn::Position { invariant: false },
117 mutable: true,
118 storage: StorageQualifier::Output,
119 },
120 "gl_FragCoord" => BuiltInData {
121 inner: TypeInner::Vector {
122 size: VectorSize::Quad,
123 scalar: Scalar::F32,
124 },
125 builtin: BuiltIn::Position { invariant: false },
126 mutable: false,
127 storage: StorageQualifier::Input,
128 },
129 "gl_PointCoord" => BuiltInData {
130 inner: TypeInner::Vector {
131 size: VectorSize::Bi,
132 scalar: Scalar::F32,
133 },
134 builtin: BuiltIn::PointCoord,
135 mutable: false,
136 storage: StorageQualifier::Input,
137 },
138 "gl_GlobalInvocationID"
139 | "gl_NumWorkGroups"
140 | "gl_WorkGroupSize"
141 | "gl_WorkGroupID"
142 | "gl_LocalInvocationID" => BuiltInData {
143 inner: TypeInner::Vector {
144 size: VectorSize::Tri,
145 scalar: Scalar::U32,
146 },
147 builtin: match name {
148 "gl_GlobalInvocationID" => BuiltIn::GlobalInvocationId,
149 "gl_NumWorkGroups" => BuiltIn::NumWorkGroups,
150 "gl_WorkGroupSize" => BuiltIn::WorkGroupSize,
151 "gl_WorkGroupID" => BuiltIn::WorkGroupId,
152 "gl_LocalInvocationID" => BuiltIn::LocalInvocationId,
153 _ => unreachable!(),
154 },
155 mutable: false,
156 storage: StorageQualifier::Input,
157 },
158 "gl_FrontFacing" => BuiltInData {
159 inner: TypeInner::Scalar(Scalar::BOOL),
160 builtin: BuiltIn::FrontFacing,
161 mutable: false,
162 storage: StorageQualifier::Input,
163 },
164 "gl_PointSize" | "gl_FragDepth" => BuiltInData {
165 inner: TypeInner::Scalar(Scalar::F32),
166 builtin: match name {
167 "gl_PointSize" => BuiltIn::PointSize,
168 "gl_FragDepth" => BuiltIn::FragDepth,
169 _ => unreachable!(),
170 },
171 mutable: true,
172 storage: StorageQualifier::Output,
173 },
174 "gl_ClipDistance" | "gl_CullDistance" => {
175 let base = ctx.module.types.insert(
176 Type {
177 name: None,
178 inner: TypeInner::Scalar(Scalar::F32),
179 },
180 meta,
181 );
182
183 BuiltInData {
184 inner: TypeInner::Array {
185 base,
186 size: crate::ArraySize::Dynamic,
187 stride: 4,
188 },
189 builtin: match name {
190 "gl_ClipDistance" => BuiltIn::ClipDistance,
191 "gl_CullDistance" => BuiltIn::CullDistance,
192 _ => unreachable!(),
193 },
194 mutable: self.meta.stage == ShaderStage::Vertex,
195 storage: StorageQualifier::Output,
196 }
197 }
198 _ => {
199 let builtin = match name {
200 "gl_BaseVertex" => BuiltIn::BaseVertex,
201 "gl_BaseInstance" => BuiltIn::BaseInstance,
202 "gl_PrimitiveID" => BuiltIn::PrimitiveIndex,
203 "gl_BaryCoordEXT" => BuiltIn::Barycentric,
204 "gl_InstanceIndex" => BuiltIn::InstanceIndex,
205 "gl_VertexIndex" => BuiltIn::VertexIndex,
206 "gl_SampleID" => BuiltIn::SampleIndex,
207 "gl_LocalInvocationIndex" => BuiltIn::LocalInvocationIndex,
208 "gl_DrawID" => BuiltIn::DrawID,
209 _ => return Ok(None),
210 };
211
212 BuiltInData {
213 inner: TypeInner::Scalar(Scalar::U32),
214 builtin,
215 mutable: false,
216 storage: StorageQualifier::Input,
217 }
218 }
219 };
220
221 self.add_builtin(ctx, name, data, meta)
222 }
223
224 pub(crate) fn make_variable_invariant(
225 &mut self,
226 ctx: &mut Context,
227 name: &str,
228 meta: Span,
229 ) -> Result<()> {
230 if let Some(var) = self.lookup_variable(ctx, name, meta)? {
231 if let Some(index) = var.entry_arg {
232 if let Binding::BuiltIn(BuiltIn::Position { ref mut invariant }) =
233 self.entry_args[index].binding
234 {
235 *invariant = true;
236 }
237 }
238 }
239 Ok(())
240 }
241
242 pub(crate) fn field_selection(
243 &mut self,
244 ctx: &mut Context,
245 pos: ExprPos,
246 expression: Handle<Expression>,
247 name: &str,
248 meta: Span,
249 ) -> Result<Handle<Expression>> {
250 let (ty, is_pointer) = match *ctx.resolve_type(expression, meta)? {
251 TypeInner::Pointer { base, .. } => (&ctx.module.types[base].inner, true),
252 ref ty => (ty, false),
253 };
254 match *ty {
255 TypeInner::Struct { ref members, .. } => {
256 let index = members
257 .iter()
258 .position(|m| m.name == Some(name.into()))
259 .ok_or_else(|| Error {
260 kind: ErrorKind::UnknownField(name.into()),
261 meta,
262 })?;
263 let pointer = ctx.add_expression(
264 Expression::AccessIndex {
265 base: expression,
266 index: index as u32,
267 },
268 meta,
269 )?;
270
271 Ok(match pos {
272 ExprPos::Rhs if is_pointer => {
273 ctx.add_expression(Expression::Load { pointer }, meta)?
274 }
275 _ => pointer,
276 })
277 }
278 TypeInner::Vector { size, .. } => {
280 let check_swizzle_components = |comps: &str| {
281 name.chars()
282 .map(|c| {
283 comps
284 .find(c)
285 .filter(|i| *i < size as usize)
286 .map(|i| SwizzleComponent::from_index(i as u32))
287 })
288 .collect::<Option<Vec<SwizzleComponent>>>()
289 };
290
291 let components = check_swizzle_components("xyzw")
292 .or_else(|| check_swizzle_components("rgba"))
293 .or_else(|| check_swizzle_components("stpq"));
294
295 if let Some(components) = components {
296 if let ExprPos::Lhs = pos {
297 let not_unique = (1..components.len())
298 .any(|i| components[i..].contains(&components[i - 1]));
299 if not_unique {
300 self.errors.push(Error {
301 kind: ErrorKind::SemanticError(
302 format!(
303 concat!(
304 "swizzle cannot have duplicate components in ",
305 "left-hand-side expression for \"{:?}\""
306 ),
307 name
308 )
309 .into(),
310 ),
311 meta,
312 })
313 }
314 }
315
316 let mut pattern = [SwizzleComponent::X; 4];
317 for (pat, component) in pattern.iter_mut().zip(&components) {
318 *pat = *component;
319 }
320
321 let mut expression = expression;
323 if let Expression::Swizzle {
324 size: _,
325 vector,
326 pattern: ref src_pattern,
327 } = ctx[expression]
328 {
329 expression = vector;
330 for pat in &mut pattern {
331 *pat = src_pattern[pat.index() as usize];
332 }
333 }
334
335 let size = match components.len() {
336 1 => {
338 match pos {
339 ExprPos::Rhs if is_pointer => {
343 expression = ctx.add_expression(
344 Expression::Load {
345 pointer: expression,
346 },
347 meta,
348 )?;
349 }
350 _ => {}
351 };
352 return ctx.add_expression(
353 Expression::AccessIndex {
354 base: expression,
355 index: pattern[0].index(),
356 },
357 meta,
358 );
359 }
360 2 => VectorSize::Bi,
361 3 => VectorSize::Tri,
362 4 => VectorSize::Quad,
363 _ => {
364 self.errors.push(Error {
365 kind: ErrorKind::SemanticError(
366 format!("Bad swizzle size for \"{name:?}\"").into(),
367 ),
368 meta,
369 });
370
371 VectorSize::Quad
372 }
373 };
374
375 if is_pointer {
376 expression = ctx.add_expression(
380 Expression::Load {
381 pointer: expression,
382 },
383 meta,
384 )?;
385 }
386
387 Ok(ctx.add_expression(
388 Expression::Swizzle {
389 size,
390 vector: expression,
391 pattern,
392 },
393 meta,
394 )?)
395 } else {
396 Err(Error {
397 kind: ErrorKind::SemanticError(
398 format!("Invalid swizzle for vector \"{name}\"").into(),
399 ),
400 meta,
401 })
402 }
403 }
404 _ => Err(Error {
405 kind: ErrorKind::SemanticError(
406 format!("Can't lookup field on this type \"{name}\"").into(),
407 ),
408 meta,
409 }),
410 }
411 }
412
413 pub(crate) fn add_global_var(
414 &mut self,
415 ctx: &mut Context,
416 VarDeclaration {
417 qualifiers,
418 mut ty,
419 name,
420 init,
421 meta,
422 }: VarDeclaration,
423 ) -> Result<GlobalOrConstant> {
424 let storage = qualifiers.storage.0;
425 let (ret, lookup) = match storage {
426 StorageQualifier::Input | StorageQualifier::Output => {
427 let input = storage == StorageQualifier::Input;
428 let location = qualifiers
431 .uint_layout_qualifier("location", &mut self.errors)
432 .unwrap_or(0);
433 let interpolation = qualifiers.interpolation.take().map(|(i, _)| i).or_else(|| {
434 let kind = ctx.module.types[ty].inner.scalar_kind()?;
435 Some(match kind {
436 ScalarKind::Float => Interpolation::Perspective,
437 _ => Interpolation::Flat,
438 })
439 });
440 let sampling = qualifiers.sampling.take().map(|(s, _)| s);
441
442 let handle = ctx.module.global_variables.append(
443 GlobalVariable {
444 name: name.clone(),
445 space: AddressSpace::Private,
446 binding: None,
447 ty,
448 init,
449 },
450 meta,
451 );
452
453 let blend_src = qualifiers
454 .layout_qualifiers
455 .remove(&QualifierKey::Index)
456 .and_then(|(value, _span)| match value {
457 QualifierValue::Uint(index) => Some(index),
458 _ => None,
459 });
460
461 let idx = self.entry_args.len();
462 self.entry_args.push(EntryArg {
463 name: name.clone(),
464 binding: Binding::Location {
465 location,
466 interpolation,
467 sampling,
468 blend_src,
469 per_primitive: false,
470 },
471 handle,
472 storage,
473 });
474
475 let lookup = GlobalLookup {
476 kind: GlobalLookupKind::Variable(handle),
477 entry_arg: Some(idx),
478 mutable: !input,
479 };
480
481 (GlobalOrConstant::Global(handle), lookup)
482 }
483 StorageQualifier::Const => {
484 let init = init.ok_or_else(|| Error {
485 kind: ErrorKind::SemanticError("const values must have an initializer".into()),
486 meta,
487 })?;
488
489 let constant = Constant {
490 name: name.clone(),
491 ty,
492 init,
493 };
494 let handle = ctx.module.constants.append(constant, meta);
495
496 let lookup = GlobalLookup {
497 kind: GlobalLookupKind::Constant(handle, ty),
498 entry_arg: None,
499 mutable: false,
500 };
501
502 (GlobalOrConstant::Constant(handle), lookup)
503 }
504 StorageQualifier::AddressSpace(mut space) => {
505 match space {
506 AddressSpace::Storage { ref mut access } => {
507 if let Some((allowed_access, _)) = qualifiers.storage_access.take() {
508 *access = allowed_access;
509 }
510 }
511 AddressSpace::Uniform => match ctx.module.types[ty].inner {
512 TypeInner::Image {
513 class,
514 dim,
515 arrayed,
516 } => {
517 if let crate::ImageClass::Storage {
518 mut access,
519 mut format,
520 } = class
521 {
522 if let Some((allowed_access, _)) = qualifiers.storage_access.take()
523 {
524 access = allowed_access;
525 }
526
527 match qualifiers.layout_qualifiers.remove(&QualifierKey::Format) {
528 Some((QualifierValue::Format(f), _)) => format = f,
529 None => self.errors.push(Error {
532 kind: ErrorKind::SemanticError(
533 "image types require a format layout qualifier".into(),
534 ),
535 meta,
536 }),
537 _ => unreachable!(),
538 }
539
540 ty = ctx.module.types.insert(
541 Type {
542 name: None,
543 inner: TypeInner::Image {
544 dim,
545 arrayed,
546 class: crate::ImageClass::Storage { format, access },
547 },
548 },
549 meta,
550 );
551 }
552
553 space = AddressSpace::Handle
554 }
555 TypeInner::Sampler { .. } => space = AddressSpace::Handle,
556 _ => {
557 if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) {
558 space = AddressSpace::PushConstant
559 }
560 }
561 },
562 AddressSpace::Function => space = AddressSpace::Private,
563 _ => {}
564 };
565
566 let binding = match space {
567 AddressSpace::Uniform | AddressSpace::Storage { .. } | AddressSpace::Handle => {
568 let binding = qualifiers.uint_layout_qualifier("binding", &mut self.errors);
569 if binding.is_none() {
570 self.errors.push(Error {
571 kind: ErrorKind::SemanticError(
572 "uniform/buffer blocks require layout(binding=X)".into(),
573 ),
574 meta,
575 });
576 }
577 let set = qualifiers.uint_layout_qualifier("set", &mut self.errors);
578 binding.map(|binding| ResourceBinding {
579 group: set.unwrap_or(0),
580 binding,
581 })
582 }
583 _ => None,
584 };
585
586 let handle = ctx.module.global_variables.append(
587 GlobalVariable {
588 name: name.clone(),
589 space,
590 binding,
591 ty,
592 init,
593 },
594 meta,
595 );
596
597 let lookup = GlobalLookup {
598 kind: GlobalLookupKind::Variable(handle),
599 entry_arg: None,
600 mutable: true,
601 };
602
603 (GlobalOrConstant::Global(handle), lookup)
604 }
605 };
606
607 if let Some(name) = name {
608 ctx.add_global(&name, lookup)?;
609
610 self.global_variables.push((name, lookup));
611 }
612
613 qualifiers.unused_errors(&mut self.errors);
614
615 Ok(ret)
616 }
617
618 pub(crate) fn add_local_var(
619 &mut self,
620 ctx: &mut Context,
621 decl: VarDeclaration,
622 ) -> Result<Handle<Expression>> {
623 let storage = decl.qualifiers.storage;
624 let mutable = match storage.0 {
625 StorageQualifier::AddressSpace(AddressSpace::Function) => true,
626 StorageQualifier::Const => false,
627 _ => {
628 self.errors.push(Error {
629 kind: ErrorKind::SemanticError("Locals cannot have a storage qualifier".into()),
630 meta: storage.1,
631 });
632 true
633 }
634 };
635
636 let handle = ctx.locals.append(
637 LocalVariable {
638 name: decl.name.clone(),
639 ty: decl.ty,
640 init: decl.init,
641 },
642 decl.meta,
643 );
644 let expr = ctx.add_expression(Expression::LocalVariable(handle), decl.meta)?;
645
646 if let Some(name) = decl.name {
647 let maybe_var = ctx.add_local_var(name.clone(), expr, mutable);
648
649 if maybe_var.is_some() {
650 self.errors.push(Error {
651 kind: ErrorKind::VariableAlreadyDeclared(name),
652 meta: decl.meta,
653 })
654 }
655 }
656
657 decl.qualifiers.unused_errors(&mut self.errors);
658
659 Ok(expr)
660 }
661}