Language3 min read

Eval

Compile-time evaluation, code folding, and compile-time expansion blocks in BWSL.

Reading Time
3 min
Word Count
488
Sections
12
Try It Live

Pressure-test the syntax

Take the concept from this page into the playground and deliberately break a pass, binding, or type signature to see how the compiler responds.

Try a Live Edit

The eval keyword enables compile-time evaluation in BWSL. It can mark functions and methods for constant folding, and it can define eval { ... } blocks inside shader bodies for compile-time statement expansion.

Eval Blocks

An eval { ... } block inside a shader body runs at compile time and expands into ordinary runtime statements. This lets you use compile-time control flow, loops, and local variables to generate shader code.

bwsl
vertex {
float sum = 0.0;
eval {
const int taps = 4;
eval int accumulator = 0;
if (taps > 2) {
sum += 1.0;
}
for (i in 0..taps) {
accumulator = accumulator + i;
sum += float(i);
}
loop (2) {
accumulator = accumulator + 1;
}
sum += float(accumulator);
}
output.position = float4(sum, 0.0, 0.0, 1.0);
}

The block above expands before IR lowering. The compiler evaluates if, for, and loop at compile time, then emits the resulting runtime statements (like sum += 1.0; and sum += float(i);) with all compile-time values substituted.

Compile-Time Locals

Inside an eval block, there are two kinds of compile-time variables:

DeclarationMutabilityUsage
const int x = 4;ImmutableCompile-time constants
eval int x = 0;MutableCompile-time accumulators, counters

const locals work the same as elsewhere — they're fixed at declaration. eval locals can be reassigned within the block:

bwsl
eval {
eval int count = 0;
for (i in 0..8) {
count = count + 1;
}
// count is now 8 at compile time
output.totalIterations = float(count);
}

Compile-Time Control Flow

All control flow inside an eval block is evaluated at compile time:

Compile-time if — branches are resolved at compile time. Only the taken branch emits runtime code:

bwsl
eval {
const int quality = 3;
if (quality > 2) {
// This branch is taken — these statements are emitted
color = highQualityLighting(N, V, L);
} else {
// This branch is eliminated entirely
color = simpleLighting(N, L);
}
}

Compile-time for — range-based iteration unrolls at compile time:

bwsl
eval {
for (i in 0..4) {
float weight = float(i) / 4.0;
sum += sample(resources.tex, resources.texSampler, uv + float2(weight, 0.0)).r;
}
}

This expands to four separate sum += statements with i substituted as 0, 1, 2, 3.

Compile-time loop — repeats a body a fixed number of times:

bwsl
eval {
loop (3) {
sum += sampleNext();
}
}

eval { ... } expands into ordinary AST nodes before IR lowering. The rest of the compiler pipeline sees standard statements — there is no runtime overhead from the eval machinery.

Runtime Statements Inside Eval

Any statement inside an eval block that isn't a compile-time construct is emitted as a runtime statement, with compile-time values substituted:

bwsl
eval {
for (i in 0..3) {
// This is a runtime statement, emitted 3 times with i = 0, 1, 2
float w = weights[i];
color += sample(resources.tex, resources.texSampler, uv + offsets[i]) * w;
}
}

Runtime declarations inside an eval block should not shadow a visible compile-time variable. Keep compile-time and runtime names distinct to avoid confusion.

Eval Methods

The eval keyword also marks enum methods for compile-time evaluation. Method bodies pattern match on the enum instance directly, and self is available inside the body without being declared as a parameter.

bwsl
enum Light {
Point(float3 position, float radius),
Directional(float3 direction),
Spot(float3 position, float3 direction, float angle),
eval getDirection :: (float3 surfacePos) -> float3 {
Point(pos, _): normalize(pos - surfacePos)
Directional(dir): -dir
Spot(pos, _, _): normalize(pos - surfacePos)
}
}

When the variant is known at compile time, the compiler folds the selected arm directly into the generated shader.

Eval If (Standalone)

Outside of eval { } blocks, eval if can be used as a standalone statement for compile-time conditional evaluation:

bwsl
enum Channels : uint {
Red = 0b0001,
Green = 0b0010,
Blue = 0b0100,
Alpha = 0b1000,
eval componentCount :: () -> int {
int count = 0;
eval if ((self & Red) != 0) count++;
eval if ((self & Green) != 0) count++;
eval if ((self & Blue) != 0) count++;
eval if ((self & Alpha) != 0) count++;
return count;
}
}

Code Folding

Small helpers can be folded into constants:

bwsl
const float PI = 3.14159265359;
eval circleArea :: (float radius) -> float {
return PI * radius * radius;
}
const float area = circleArea(2.0);

Pattern Arms

The same arm syntax used by enum methods also appears in free functions:

bwsl
distance :: (SDFShape shape, float3 p) -> float {
Sphere(r): length(p) - r
Box(size): boxSDF(p, size)
Torus(major, minor): torusSDF(p, major, minor)
}

Best Practices

Do use eval for:

  • eval { } blocks to generate repetitive code (blur kernels, tap loops, feature toggles)
  • Enum dispatch methods
  • Compile-time configuration branching
  • Constant folding opportunities
  • Small helper logic with compile-time inputs

Don't use eval for:

  • Logic that depends on runtime values — everything inside eval { } control flow must be compile-time known
  • Large deeply-nested expansions that produce hard-to-debug generated code
  • Public examples based on unsupported syntax such as explicit self parameters

See Also

  • Enums — Algebraic data types with eval methods
  • Loops — Iteration constructs including eval for and eval loop
  • Language Overview — BWSL syntax and concepts