Loops and Branches

Control Flow with if, loop, for and while

if, for and while read as in C, but braces are required and conditions must be bool (if (n) with an i32 gave "if statement condition must be bool, got i32"). WGSL adds loop, whose optional continuing block runs after every iteration and may end with break if (a do-while is loop { ...; continuing { break if done; } }):

Three loops over BookNest's page countsJavaScript
// out: 3 x u32
@group(0) @binding(0) var<storage, read_write> out: array<u32>;
const pages = array(312u, 428u, 256u, 198u, 344u, 176u);
@compute @workgroup_size(1) fn main() {
  var total = 0u;
  for (var i = 0u; i < 6; i++) { total += pages[i]; }      // for
  var n = 0u;
  while (n < 6 && pages[n] != 198) { n++; }                // while: index of the 198-page book
  var halvings = 0u;
  var p = 428u;
  loop {                                                    // loop with continuing
    if (p < 10) { break; }
    continuing { p /= 2; halvings++; }
  }
  out[0] = total; out[1] = n; out[2] = halvings;
}
Output
out: 1714, 3, 6

&& and || short-circuit, so pages[n] is never read with n == 6. When invocations that run together take different branches, the GPU runs both sides with some lanes masked off, so prefer select() for short branches in hot loops.

A for loop totals BookNest's page counts, a while loop finds the 198-page book, and loop with continuing halves 428HTMLLive
<!doctype html>
<style>
  body { margin: 0; background: #f7f4ee; font: 14px system-ui, sans-serif; }
  .stage { position: relative; width: 100%; max-width: 600px; }
  .stage canvas { display: block; width: 100%; }
  .stage canvas + canvas { position: absolute; inset: 0; pointer-events: none; }
</style>
<div class="stage">
  <canvas id="view" width="600" height="340"></canvas>
  <canvas id="labels" width="600" height="340"></canvas>
</div>
<script>
const canvas = document.getElementById('view');
const ink = document.getElementById('labels').getContext('2d');

function showMessage(text) {                     // 2D fallback when WebGPU is missing
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = '#fbeaea'; ctx.fillRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#8a2b2b'; ctx.font = '18px system-ui, sans-serif'; ctx.textAlign = 'center';
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);
}
function label(text, x, y, size = 12, color = '#2b2b2b', align = 'left', weight = '') {
  ink.font = `${weight} ${size}px system-ui, sans-serif`; ink.fillStyle = color; ink.textAlign = align; ink.fillText(text, x, y);
}

// The book's three loops, plus the halving sequence recorded in out[3..] for the picture.
const shared = /* wgsl */ `
const pages = array(312u, 428u, 256u, 198u, 344u, 176u);`;
const lab = shared + /* wgsl */ `
@group(0) @binding(0) var<storage, read_write> out: array<u32>;
@compute @workgroup_size(1) fn main() {
  var total = 0u;
  for (var i = 0u; i < 6; i++) { total += pages[i]; }      // for
  var n = 0u;
  while (n < 6 && pages[n] != 198) { n++; }                // while: index of the 198-page book
  var halvings = 0u;
  var p = 428u;
  loop {                                                    // loop with continuing
    out[3 + halvings] = p;
    if (p < 10) { break; }
    continuing { p /= 2; halvings++; }
  }
  out[0] = total; out[1] = n; out[2] = halvings;
}`;
const view = shared + /* wgsl */ `
@group(0) @binding(0) var<storage> out: array<u32>;
struct Out { @builtin(position) pos: vec4f, @location(0) color: vec3f }
@vertex fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) i: u32) -> Out {
  let q = vec2f(f32(v & 1), f32(v >> 1));
  var px: vec2f;
  var color: vec3f;
  if (i < 6) {                                             // six spines, heights = page counts
    px = vec2f(30 + f32(i) * 44 + q.x * 34, 250 - q.y * f32(pages[i]) * 0.45);
    color = select(vec3f(0.08, 0.40, 0.75), vec3f(0.88, 0.55, 0.15), i == out[1]);
  } else {                                                 // the halving sequence as bars
    let k = i - 6;
    let shown = f32(k <= out[2]);
    px = vec2f(330 + f32(k) * 34 + q.x * 26 * shown, 250 - q.y * f32(out[3 + k]) * 0.45 * shown);
    color = vec3f(0.55, 0.30, 0.65);
  }
  return Out(vec4f(px.x / 300 - 1, 1 - px.y / 170, 0, 1), color);
}
@fragment fn fs(in: Out) -> @location(0) vec4f { return vec4f(in.color, 1); }`;

async function main() {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return showMessage('WebGPU is not available in this browser');
  const device = await adapter.requestDevice();
  const context = canvas.getContext('webgpu');
  const format = navigator.gpu.getPreferredCanvasFormat();
  context.configure({ device, format });
  const B = GPUBufferUsage, SIZE = 16 * 4;
  const out = device.createBuffer({ size: SIZE, usage: B.STORAGE | B.COPY_SRC });
  const read = device.createBuffer({ size: SIZE, usage: B.COPY_DST | B.MAP_READ });
  const compute = device.createComputePipeline({ layout: 'auto', compute: { module: device.createShaderModule({ code: lab }) } });
  const module = device.createShaderModule({ code: view });
  const render = device.createRenderPipeline({ layout: 'auto', primitive: { topology: 'triangle-strip' },
    vertex: { module }, fragment: { module, targets: [{ format }] } });

  const encoder = device.createCommandEncoder();
  const cp = encoder.beginComputePass();
  cp.setPipeline(compute);
  cp.setBindGroup(0, device.createBindGroup({ layout: compute.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: out } }] }));
  cp.dispatchWorkgroups(1);
  cp.end();
  const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(),
    clearValue: [0.97, 0.96, 0.93, 1], loadOp: 'clear', storeOp: 'store' }] });
  pass.setPipeline(render);
  pass.setBindGroup(0, device.createBindGroup({ layout: render.getBindGroupLayout(0), entries: [{ binding: 0, resource: { buffer: out } }] }));
  pass.draw(4, 6 + 8);
  pass.end();
  encoder.copyBufferToBuffer(out, 0, read, 0, SIZE);
  device.queue.submit([encoder.finish()]);
  await read.mapAsync(GPUMapMode.READ);
  const o = new Uint32Array(read.getMappedRange().slice(0));
  read.unmap();

  label('Page counts', 30, 30, 13, '#222', 'left', 'bold');
  [312, 428, 256, 198, 344, 176].forEach((p, i) => label(String(p), 47 + i * 44, 268, 11, '#444', 'center'));
  label(`for: total = ${o[0]} pages`, 30, 296, 12.5, '#1f4f8a');
  label(`while: the 198-page book is index ${o[1]} (orange)`, 30, 316, 12.5, '#8a5a00');
  label('loop { ... continuing { p /= 2 } }', 330, 30, 13, '#222', 'left', 'bold');
  for (let k = 0; k <= o[2]; k++) label(String(o[3 + k]), 343 + k * 34, 268, 11, '#444', 'center');
  label(`break when p < 10: halvings = ${o[2]}`, 330, 296, 12.5, '#5b2b6e');
}
main();
</script>