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_InstanceIndex" => BuiltIn::InstanceIndex,
204 "gl_VertexIndex" => BuiltIn::VertexIndex,
205 "gl_SampleID" => BuiltIn::SampleIndex,
206 "gl_LocalInvocationIndex" => BuiltIn::LocalInvocationIndex,
207 "gl_DrawID" => BuiltIn::DrawID,
208 _ => return Ok(None),
209 };
210
211 BuiltInData {
212 inner: TypeInner::Scalar(Scalar::U32),
213 builtin,
214 mutable: false,
215 storage: StorageQualifier::Input,
216 }
217 }
218 };
219
220 self.add_builtin(ctx, name, data, meta)
221 }
222
223 pub(crate) fn make_variable_invariant(
224 &mut self,
225 ctx: &mut Context,
226 name: &str,
227 meta: Span,
228 ) -> Result<()> {
229 if let Some(var) = self.lookup_variable(ctx, name, meta)? {
230 if let Some(index) = var.entry_arg {
231 if let Binding::BuiltIn(BuiltIn::Position { ref mut invariant }) =
232 self.entry_args[index].binding
233 {
234 *invariant = true;
235 }
236 }
237 }
238 Ok(())
239 }
240
241 pub(crate) fn field_selection(
242 &mut self,
243 ctx: &mut Context,
244 pos: ExprPos,
245 expression: Handle<Expression>,
246 name: &str,
247 meta: Span,
248 ) -> Result<Handle<Expression>> {
249 let (ty, is_pointer) = match *ctx.resolve_type(expression, meta)? {
250 TypeInner::Pointer { base, .. } => (&ctx.module.types[base].inner, true),
251 ref ty => (ty, false),
252 };
253 match *ty {
254 TypeInner::Struct { ref members, .. } => {
255 let index = members
256 .iter()
257 .position(|m| m.name == Some(name.into()))
258 .ok_or_else(|| Error {
259 kind: ErrorKind::UnknownField(name.into()),
260 meta,
261 })?;
262 let pointer = ctx.add_expression(
263 Expression::AccessIndex {
264 base: expression,
265 index: index as u32,
266 },
267 meta,
268 )?;
269
270 Ok(match pos {
271 ExprPos::Rhs if is_pointer => {
272 ctx.add_expression(Expression::Load { pointer }, meta)?
273 }
274 _ => pointer,
275 })
276 }
277 TypeInner::Vector { size, .. } => {
279 let check_swizzle_components = |comps: &str| {
280 name.chars()
281 .map(|c| {
282 comps
283 .find(c)
284 .filter(|i| *i < size as usize)
285 .map(|i| SwizzleComponent::from_index(i as u32))
286 })
287 .collect::<Option<Vec<SwizzleComponent>>>()
288 };
289
290 let components = check_swizzle_components("xyzw")
291 .or_else(|| check_swizzle_components("rgba"))
292 .or_else(|| check_swizzle_components("stpq"));
293
294 if let Some(components) = components {
295 if let ExprPos::Lhs = pos {
296 let not_unique = (1..components.len())
297 .any(|i| components[i..].contains(&components[i - 1]));
298 if not_unique {
299 self.errors.push(Error {
300 kind: ErrorKind::SemanticError(
301 format!(
302 concat!(
303 "swizzle cannot have duplicate components in ",
304 "left-hand-side expression for \"{:?}\""
305 ),
306 name
307 )
308 .into(),
309 ),
310 meta,
311 })
312 }
313 }
314
315 let mut pattern = [SwizzleComponent::X; 4];
316 for (pat, component) in pattern.iter_mut().zip(&components) {
317 *pat = *component;
318 }
319
320 let mut expression = expression;
322 if let Expression::Swizzle {
323 size: _,
324 vector,
325 pattern: ref src_pattern,
326 } = ctx[expression]
327 {
328 expression = vector;
329 for pat in &mut pattern {
330 *pat = src_pattern[pat.index() as usize];
331 }
332 }
333
334 let size = match components.len() {
335 1 => {
337 match pos {
338 ExprPos::Rhs if is_pointer => {
342 expression = ctx.add_expression(
343 Expression::Load {
344 pointer: expression,
345 },
346 meta,
347 )?;
348 }
349 _ => {}
350 };
351 return ctx.add_expression(
352 Expression::AccessIndex {
353 base: expression,
354 index: pattern[0].index(),
355 },
356 meta,
357 );
358 }
359 2 => VectorSize::Bi,
360 3 => VectorSize::Tri,
361 4 => VectorSize::Quad,
362 _ => {
363 self.errors.push(Error {
364 kind: ErrorKind::SemanticError(
365 format!("Bad swizzle size for \"{name:?}\"").into(),
366 ),
367 meta,
368 });
369
370 VectorSize::Quad
371 }
372 };
373
374 if is_pointer {
375 expression = ctx.add_expression(
379 Expression::Load {
380 pointer: expression,
381 },
382 meta,
383 )?;
384 }
385
386 Ok(ctx.add_expression(
387 Expression::Swizzle {
388 size,
389 vector: expression,
390 pattern,
391 },
392 meta,
393 )?)
394 } else {
395 Err(Error {
396 kind: ErrorKind::SemanticError(
397 format!("Invalid swizzle for vector \"{name}\"").into(),
398 ),
399 meta,
400 })
401 }
402 }
403 _ => Err(Error {
404 kind: ErrorKind::SemanticError(
405 format!("Can't lookup field on this type \"{name}\"").into(),
406 ),
407 meta,
408 }),
409 }
410 }
411
412 pub(crate) fn add_global_var(
413 &mut self,
414 ctx: &mut Context,
415 VarDeclaration {
416 qualifiers,
417 mut ty,
418 name,
419 init,
420 meta,
421 }: VarDeclaration,
422 ) -> Result<GlobalOrConstant> {
423 let storage = qualifiers.storage.0;
424 let (ret, lookup) = match storage {
425 StorageQualifier::Input | StorageQualifier::Output => {
426 let input = storage == StorageQualifier::Input;
427 let location = qualifiers
430 .uint_layout_qualifier("location", &mut self.errors)
431 .unwrap_or(0);
432 let interpolation = qualifiers.interpolation.take().map(|(i, _)| i).or_else(|| {
433 let kind = ctx.module.types[ty].inner.scalar_kind()?;
434 Some(match kind {
435 ScalarKind::Float => Interpolation::Perspective,
436 _ => Interpolation::Flat,
437 })
438 });
439 let sampling = qualifiers.sampling.take().map(|(s, _)| s);
440
441 let handle = ctx.module.global_variables.append(
442 GlobalVariable {
443 name: name.clone(),
444 space: AddressSpace::Private,
445 binding: None,
446 ty,
447 init,
448 },
449 meta,
450 );
451
452 let blend_src = qualifiers
453 .layout_qualifiers
454 .remove(&QualifierKey::Index)
455 .and_then(|(value, _span)| match value {
456 QualifierValue::Uint(index) => Some(index),
457 _ => None,
458 });
459
460 let idx = self.entry_args.len();
461 self.entry_args.push(EntryArg {
462 name: name.clone(),
463 binding: Binding::Location {
464 location,
465 interpolation,
466 sampling,
467 blend_src,
468 },
469 handle,
470 storage,
471 });
472
473 let lookup = GlobalLookup {
474 kind: GlobalLookupKind::Variable(handle),
475 entry_arg: Some(idx),
476 mutable: !input,
477 };
478
479 (GlobalOrConstant::Global(handle), lookup)
480 }
481 StorageQualifier::Const => {
482 let init = init.ok_or_else(|| Error {
483 kind: ErrorKind::SemanticError("const values must have an initializer".into()),
484 meta,
485 })?;
486
487 let constant = Constant {
488 name: name.clone(),
489 ty,
490 init,
491 };
492 let handle = ctx.module.constants.append(constant, meta);
493
494 let lookup = GlobalLookup {
495 kind: GlobalLookupKind::Constant(handle, ty),
496 entry_arg: None,
497 mutable: false,
498 };
499
500 (GlobalOrConstant::Constant(handle), lookup)
501 }
502 StorageQualifier::AddressSpace(mut space) => {
503 match space {
504 AddressSpace::Storage { ref mut access } => {
505 if let Some((allowed_access, _)) = qualifiers.storage_access.take() {
506 *access = allowed_access;
507 }
508 }
509 AddressSpace::Uniform => match ctx.module.types[ty].inner {
510 TypeInner::Image {
511 class,
512 dim,
513 arrayed,
514 } => {
515 if let crate::ImageClass::Storage {
516 mut access,
517 mut format,
518 } = class
519 {
520 if let Some((allowed_access, _)) = qualifiers.storage_access.take()
521 {
522 access = allowed_access;
523 }
524
525 match qualifiers.layout_qualifiers.remove(&QualifierKey::Format) {
526 Some((QualifierValue::Format(f), _)) => format = f,
527 None => self.errors.push(Error {
530 kind: ErrorKind::SemanticError(
531 "image types require a format layout qualifier".into(),
532 ),
533 meta,
534 }),
535 _ => unreachable!(),
536 }
537
538 ty = ctx.module.types.insert(
539 Type {
540 name: None,
541 inner: TypeInner::Image {
542 dim,
543 arrayed,
544 class: crate::ImageClass::Storage { format, access },
545 },
546 },
547 meta,
548 );
549 }
550
551 space = AddressSpace::Handle
552 }
553 TypeInner::Sampler { .. } => space = AddressSpace::Handle,
554 _ => {
555 if qualifiers.none_layout_qualifier("push_constant", &mut self.errors) {
556 space = AddressSpace::PushConstant
557 }
558 }
559 },
560 AddressSpace::Function => space = AddressSpace::Private,
561 _ => {}
562 };
563
564 let binding = match space {
565 AddressSpace::Uniform | AddressSpace::Storage { .. } | AddressSpace::Handle => {
566 let binding = qualifiers.uint_layout_qualifier("binding", &mut self.errors);
567 if binding.is_none() {
568 self.errors.push(Error {
569 kind: ErrorKind::SemanticError(
570 "uniform/buffer blocks require layout(binding=X)".into(),
571 ),
572 meta,
573 });
574 }
575 let set = qualifiers.uint_layout_qualifier("set", &mut self.errors);
576 binding.map(|binding| ResourceBinding {
577 group: set.unwrap_or(0),
578 binding,
579 })
580 }
581 _ => None,
582 };
583
584 let handle = ctx.module.global_variables.append(
585 GlobalVariable {
586 name: name.clone(),
587 space,
588 binding,
589 ty,
590 init,
591 },
592 meta,
593 );
594
595 let lookup = GlobalLookup {
596 kind: GlobalLookupKind::Variable(handle),
597 entry_arg: None,
598 mutable: true,
599 };
600
601 (GlobalOrConstant::Global(handle), lookup)
602 }
603 };
604
605 if let Some(name) = name {
606 ctx.add_global(&name, lookup)?;
607
608 self.global_variables.push((name, lookup));
609 }
610
611 qualifiers.unused_errors(&mut self.errors);
612
613 Ok(ret)
614 }
615
616 pub(crate) fn add_local_var(
617 &mut self,
618 ctx: &mut Context,
619 decl: VarDeclaration,
620 ) -> Result<Handle<Expression>> {
621 let storage = decl.qualifiers.storage;
622 let mutable = match storage.0 {
623 StorageQualifier::AddressSpace(AddressSpace::Function) => true,
624 StorageQualifier::Const => false,
625 _ => {
626 self.errors.push(Error {
627 kind: ErrorKind::SemanticError("Locals cannot have a storage qualifier".into()),
628 meta: storage.1,
629 });
630 true
631 }
632 };
633
634 let handle = ctx.locals.append(
635 LocalVariable {
636 name: decl.name.clone(),
637 ty: decl.ty,
638 init: decl.init,
639 },
640 decl.meta,
641 );
642 let expr = ctx.add_expression(Expression::LocalVariable(handle), decl.meta)?;
643
644 if let Some(name) = decl.name {
645 let maybe_var = ctx.add_local_var(name.clone(), expr, mutable);
646
647 if maybe_var.is_some() {
648 self.errors.push(Error {
649 kind: ErrorKind::VariableAlreadyDeclared(name),
650 meta: decl.meta,
651 })
652 }
653 }
654
655 decl.qualifiers.unused_errors(&mut self.errors);
656
657 Ok(expr)
658 }
659}