Module types

Module types 

Source
Expand description

Type-aware post-translator passes for the HLSL→WGSL pipeline.

After the regex rewrites in lib.rs produce something WGSL-shaped, the remaining failures cluster around HLSL semantics WGSL doesn’t honour:

  1. Implicit scalar→vector broadcasts in calls like clamp(<vec3>, 0, 1). HLSL broadcasts the scalars 0 and 1 to vec3(0) and vec3(1) automatically; WGSL refuses with inconsistent type passed as argument #2 to clamp.
  2. Implicit scalar←vector truncation in declarations like float lum = GetPixel(uv) * c.x + …;. HLSL takes the first component of the vec result; WGSL refuses with the type of lum is expected to be f32; but got vec3<f32>.

This module fixes both with two passes that share a small symbol table built from the translated source’s var NAME: TYPE and let NAME: TYPE declarations. Type inference for arbitrary expressions is intentionally heuristic: it returns a confident answer for the dominant MD2 patterns (named locals, numeric literals, vec/ mat constructors, calls to a small set of known helpers) and Unknown otherwise. Unknown short-circuits both passes — the translator only injects fixes it’s sure about.

Structs§

SymbolTable
Maps locally-declared identifier → its WGSL type. Pre-seeded with the uniforms and helper-function bindings the codegen wrapper exposes inside fs_main (so that texsize, q1..q32, aspect, etc. resolve to known types).

Enums§

ArgWrap 🔒
WgslType
WGSL types we care about for MD2 user shaders. Anything outside this set falls through as WgslType::Unknown and skips both passes.

Constants§

BROADCAST_BUILTINS 🔒
Built-in functions that require all arguments to share a type and don’t accept HLSL-style scalar↔vector broadcasts in WGSL. For each call to one of these, if any arg has a known vec type and another is a scalar, the scalar is wrapped in the matching vec constructor.
POLY_BUILTINS 🔒
Builtins whose return type matches their arg type (HLSL “polymorphic” element-typed functions). Used by SymbolTable::infer_expr_type — the same set as BROADCAST_BUILTINS plus a few more pure-passthrough math functions we don’t need to broadcast-rewrite but whose return type is “same as arg”.
WRAPPER_PRELUDE_LOCALS 🔒
Wrapper preamble locals exposed to user shaders. Mirrors the let aliases in onedrop-codegen::wrap_user_comp_shader::USER_COMP_FRAGMENT_PREFIX.

Functions§

all_unique 🔒
arg_wrap 🔒
Decide how to coerce an arg of arg_ty to target (the smallest vec among the call’s args). Scalars get broadcast; larger vecs get truncated; equal vecs and unknowns mostly pass through (except for literal scalars, which we still broadcast even when type inference returned Unknown).
constructor_type 🔒
inject_assignment_coercions
Walk the source for <bare_ident> <op>= <expr>; statements (where <op> is empty for plain assignment or one of + - * / for compound assignment). When the inferred type of <expr> doesn’t match the declared type of <bare_ident>, inject the HLSL implicit conversion (broadcast or truncate).
inject_broadcasts
Walk src looking for calls to broadcast-prone builtins; rewrite scalar arguments to vec constructors where the dominant arg is a vec.
inject_swizzle_assignments
Rewrite target.<swizzle> <op>= <rhs>; into a full-vector reconstruction. WGSL refuses multi-component swizzles on the LHS of an assignment (invalid left-hand side of assignment); HLSL allows them freely. The dominant cases in test-presets-200/:
inject_truncations
Walk var X: TYPE = <expr>; (or let) declarations and fix HLSL- implicit-conversion mismatches between LHS type and RHS type:
is_identifier 🔒
is_numeric_literal 🔒
is_swizzle_components 🔒
keyword_at 🔒
known_call_return_type 🔒
narrower 🔒
Reverse of widen — pick the smaller vec when both args are vecs. Used by the broadcast pass so a max(vec3, vec4) call truncates the vec4 to vec3 (HLSL semantics), not the other way round.
normalise_swizzle 🔒
Map r/g/b/a to x/y/z/w while preserving identity for x/y/z/w. MD2 user shaders mix the two conventions freely; the codegen wrapper always uses xyzw for its locals, so canonicalising on emit keeps downstream lane lookups stable.
split_binop_operands 🔒
Split an expression on top-level binary ops + - * / and return the operands. Returns None if no top-level op found (i.e. the expression is a single term).
split_call 🔒
Split <head>(<args>) into (head, args). Returns None if the expression isn’t a bare call (e.g. has a trailing .foo).
split_last_swizzle 🔒
Return (prefix, components) where <expr>.<components> is the swizzle. Splits on the last . at depth 0.
split_top_level_commas 🔒
strip_comments 🔒
Replace /* ... */ and // ... comments with spaces in-place so the resulting string has the same length and column positions as the original — crucial because SymbolTable::infer_expr_type doesn’t remap offsets when reasoning about an arg’s text.
strip_outer_parens 🔒
swizzle_target_type 🔒
vec_of_size 🔒
Returns the WgslType for a vec of the given component count. 1 maps to scalar f32, 2..4 to the matching vec type, anything else to Unknown. Convenience for AST-driven rewrites that need to construct a target type from an inferred component count.
vec_size 🔒
Component count for vec types, 0 for everything else. Used by the truncation pass to size the trailing .xy/.xyz swizzle when narrowing a wider vec into a smaller one.
widen 🔒
Pick the “widest” of two types — vec wins over scalar; among vecs keep the larger; on conflict default to the first non-unknown.