Add vector assignment execution tests (#4703)

Adds CTS execution tests for vector assignment statements, including
component access, constant and dynamic indexing, compound assignment,
whole-vector assignment, and evaluation order across address spaces
and memory views.

Issue: https://crbug.com/544734265
diff --git a/src/webgpu/shader/execution/statement/assignment/vector.spec.ts b/src/webgpu/shader/execution/statement/assignment/vector.spec.ts
new file mode 100644
index 0000000..be904ce
--- /dev/null
+++ b/src/webgpu/shader/execution/statement/assignment/vector.spec.ts
@@ -0,0 +1,699 @@
+export const description = `
+Execution tests for vector assignment statements.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { TypedArrayBufferView } from '../../../../../common/util/util.js';
+import { Float16Array } from '../../../../../external/petamoriken/float16/float16.js';
+import { AllFeaturesMaxLimitsGPUTest, GPUTest } from '../../../../gpu_test.js';
+import { align } from '../../../../util/math.js';
+import { runFlowControlTest } from '../../flow_control/harness.js';
+
+export const g = makeTestGroup(AllFeaturesMaxLimitsGPUTest);
+
+type VectorElementType = 'bool' | 'i32' | 'u32' | 'f32' | 'f16';
+type VectorWidth = 2 | 3 | 4;
+type IndexType = 'i32' | 'u32';
+type ComponentName = 'x' | 'y' | 'z' | 'w' | 'r' | 'g' | 'b' | 'a';
+type AddressSpace = 'function' | 'private' | 'workgroup' | 'storage';
+type MemoryView = 'ref' | 'ptr';
+type CompoundOp = '+=' | '*=';
+
+/** Mapping from component name to corresponding element index. */
+const kComponentIndices: Record<ComponentName, number> = {
+  x: 0,
+  y: 1,
+  z: 2,
+  w: 3,
+  r: 0,
+  g: 1,
+  b: 2,
+  a: 3,
+};
+
+const kComponentSets: Record<'xyzw' | 'rgba', readonly ComponentName[]> = {
+  xyzw: ['x', 'y', 'z', 'w'],
+  rgba: ['r', 'g', 'b', 'a'],
+};
+
+const kCompoundOps: Record<'add' | 'mul', CompoundOp> = {
+  add: '+=',
+  mul: '*=',
+};
+
+/**
+ * Parameters for running a vector element write execution test.
+ */
+interface RunVectorElementWriteArgs {
+  elementType: VectorElementType;
+  width: VectorWidth;
+  addressSpace: AddressSpace;
+  memoryView: MemoryView;
+  access:
+    | { kind: 'component'; component: ComponentName }
+    | { kind: 'constant_index'; index: number }
+    | { kind: 'dynamic_index'; index: number; indexType: IndexType };
+  compoundOp?: CompoundOp;
+}
+
+/**
+ * Options for executing a vector compute shader and verifying output.
+ */
+interface RunVectorComputeOptions {
+  wgsl: string;
+  hostBuffer: ArrayBuffer;
+  addressSpace: AddressSpace;
+  elementType: VectorElementType;
+  expectedValues: readonly number[];
+}
+
+/**
+ * Builds the compute pipeline, initializes input and output buffers, dispatches
+ * the compute pass, and verifies the output buffer values against expected values.
+ */
+function runVectorComputeAndVerify(t: GPUTest, options: RunVectorComputeOptions) {
+  const pipeline = t.device.createComputePipeline({
+    layout: 'auto',
+    compute: {
+      module: t.device.createShaderModule({ code: options.wgsl }),
+      entryPoint: 'main',
+    },
+  });
+
+  const inputBuffer = t.createBufferTracked({
+    size: 64,
+    usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
+  });
+  t.queue.writeBuffer(inputBuffer, 0, options.hostBuffer);
+
+  const outputBuffer = t.createBufferTracked({
+    size: 64,
+    usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
+  });
+
+  const bindGroupEntries: GPUBindGroupEntry[] = [
+    { binding: 0, resource: { buffer: inputBuffer } },
+    { binding: 1, resource: { buffer: outputBuffer } },
+  ];
+
+  if (options.addressSpace === 'storage') {
+    const storageBuffer = t.createBufferTracked({
+      size: 64,
+      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
+    });
+    bindGroupEntries.push({ binding: 2, resource: { buffer: storageBuffer } });
+  }
+
+  const bindGroup = t.device.createBindGroup({
+    layout: pipeline.getBindGroupLayout(0),
+    entries: bindGroupEntries,
+  });
+
+  const encoder = t.device.createCommandEncoder();
+  const pass = encoder.beginComputePass();
+  pass.setPipeline(pipeline);
+  pass.setBindGroup(0, bindGroup);
+  pass.dispatchWorkgroups(1);
+  pass.end();
+  t.queue.submit([encoder.finish()]);
+
+  let outputArrayConstructor: { new (values: readonly number[]): TypedArrayBufferView };
+  switch (options.elementType) {
+    case 'u32':
+    case 'bool':
+      outputArrayConstructor = Uint32Array;
+      break;
+    case 'i32':
+      outputArrayConstructor = Int32Array;
+      break;
+    case 'f32':
+      outputArrayConstructor = Float32Array;
+      break;
+    case 'f16':
+      outputArrayConstructor = Float16Array;
+      break;
+  }
+
+  t.expectGPUBufferValuesEqual(outputBuffer, new outputArrayConstructor(options.expectedValues));
+}
+
+/**
+ * Builds, runs, and checks the output of a vector element assignment test.
+ *
+ * This function tests that assigning to a single element of a vector variable
+ * (using a component accessor, a constant index, or a dynamic index) in various address spaces
+ * correctly updates that element while preserving the values of all other untouched elements.
+ *
+ * @param t The test fixture object.
+ * @param args The test configuration arguments.
+ */
+function runVectorElementWriteTest(t: GPUTest, args: RunVectorElementWriteArgs) {
+  // Check whether optional device features are required.
+  if (args.elementType === 'f16') {
+    t.skipIfDeviceDoesNotHaveFeature('shader-f16');
+  }
+
+  // Determine the target element index being updated.
+  let targetIndex: number;
+  if (args.access.kind === 'component') {
+    targetIndex = kComponentIndices[args.access.component];
+  } else {
+    targetIndex = args.access.index;
+  }
+
+  // Populate distinct initial values and a replacement value so that modifying
+  // any element or failing to modify is clearly detectable.
+  let initialValues: number[];
+  let replacementValue: number;
+
+  switch (args.elementType) {
+    case 'bool':
+      initialValues = [0, 0, 0, 0].slice(0, args.width);
+      replacementValue = 1;
+      break;
+    case 'i32':
+      initialValues = [-10, -20, -30, -40].slice(0, args.width);
+      replacementValue = -5;
+      break;
+    case 'u32':
+      initialValues = [10, 20, 30, 40].slice(0, args.width);
+      replacementValue = 5;
+      break;
+    case 'f32':
+    case 'f16':
+      initialValues = [10.0, 20.0, 30.0, 40.0].slice(0, args.width);
+      replacementValue = 2.5;
+      break;
+  }
+
+  const expectedValues = [...initialValues];
+  if (args.compoundOp === '+=') {
+    expectedValues[targetIndex] += replacementValue;
+  } else if (args.compoundOp === '*=') {
+    expectedValues[targetIndex] *= replacementValue;
+  } else {
+    expectedValues[targetIndex] = replacementValue;
+  }
+
+  const storageElemType = args.elementType === 'bool' ? 'u32' : args.elementType;
+  const outputElemType = args.elementType === 'bool' ? 'u32' : args.elementType;
+  const vecType = `vec${args.width}<${args.elementType}>`;
+  const storageVecType = `vec${args.width}<${storageElemType}>`;
+
+  const varRef = args.addressSpace === 'storage' ? 'storage_buffer.v' : 'v';
+  const target = args.memoryView === 'ptr' ? '(*ptr)' : varRef;
+  const ptrDecl = args.memoryView === 'ptr' ? `let ptr = &${varRef};\n  ` : '';
+
+  let lhs: string;
+  let structIndexField = '';
+
+  if (args.access.kind === 'component') {
+    lhs = `${target}.${args.access.component}`;
+  } else if (args.access.kind === 'constant_index') {
+    lhs = `${target}[${args.access.index}]`;
+  } else {
+    structIndexField = `index : ${args.access.indexType},`;
+    lhs = `${target}[inputs.index]`;
+  }
+
+  const rhs = args.elementType === 'bool' ? 'inputs.value != 0u' : 'inputs.value';
+  const op = args.compoundOp ?? '=';
+  const assignmentStatement = `${ptrDecl}${lhs} ${op} ${rhs};`;
+
+  // Construct WGSL compute shader.
+  const wgsl = `
+${args.elementType === 'f16' ? 'enable f16;' : ''}
+
+struct Inputs {
+  initial_vector : ${storageVecType},
+  value : ${storageElemType},
+  ${structIndexField}
+};
+
+@group(0) @binding(0) var<uniform> inputs : Inputs;
+
+struct Outputs {
+  data : array<${outputElemType}>,
+};
+
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+${
+  args.addressSpace === 'storage'
+    ? `struct Storage {
+  v : ${storageVecType},
+};
+@group(0) @binding(2) var<storage, read_write> storage_buffer : Storage;`
+    : ''
+}
+${args.addressSpace === 'private' ? `var<private> v : ${vecType};` : ''}
+${args.addressSpace === 'workgroup' ? `var<workgroup> v : ${vecType};` : ''}
+
+@compute @workgroup_size(1)
+fn main() {
+  ${args.addressSpace === 'function' ? `var v : ${vecType};` : ''}
+  ${
+    args.addressSpace === 'storage'
+      ? `storage_buffer.v = inputs.initial_vector;`
+      : args.elementType === 'bool'
+      ? `${varRef} = inputs.initial_vector != vec${args.width}<u32>(0u);`
+      : `${varRef} = inputs.initial_vector;`
+  }
+
+  ${assignmentStatement}
+
+  for (var i = 0u; i < ${args.width}u; i++) {
+    outputs.data[i] = ${args.elementType === 'bool' ? `u32(${varRef}[i])` : `${varRef}[i]`};
+  }
+}
+`;
+
+  // Prepare input buffer data matching WGSL struct natural alignment.
+  const hostBuffer = new ArrayBuffer(64);
+  const elemBytes = args.elementType === 'f16' ? 2 : 4;
+  const valueOffset = args.width * elemBytes;
+
+  if (args.elementType === 'f16') {
+    const f16Init = new Float16Array(hostBuffer, 0, args.width);
+    for (let i = 0; i < args.width; i++) {
+      f16Init[i] = initialValues[i];
+    }
+    const f16Val = new Float16Array(hostBuffer, valueOffset, 1);
+    f16Val[0] = replacementValue;
+  } else if (args.elementType === 'f32') {
+    const f32Init = new Float32Array(hostBuffer, 0, args.width);
+    for (let i = 0; i < args.width; i++) {
+      f32Init[i] = initialValues[i];
+    }
+    const f32Val = new Float32Array(hostBuffer, valueOffset, 1);
+    f32Val[0] = replacementValue;
+  } else if (args.elementType === 'i32') {
+    const i32Init = new Int32Array(hostBuffer, 0, args.width);
+    for (let i = 0; i < args.width; i++) {
+      i32Init[i] = initialValues[i];
+    }
+    const i32Val = new Int32Array(hostBuffer, valueOffset, 1);
+    i32Val[0] = replacementValue;
+  } else {
+    const u32Init = new Uint32Array(hostBuffer, 0, args.width);
+    for (let i = 0; i < args.width; i++) {
+      u32Init[i] = initialValues[i];
+    }
+    const u32Val = new Uint32Array(hostBuffer, valueOffset, 1);
+    u32Val[0] = replacementValue;
+  }
+
+  if (args.access.kind === 'dynamic_index') {
+    const indexOffset = align((args.width + 1) * elemBytes, 4);
+    if (args.access.indexType === 'i32') {
+      new Int32Array(hostBuffer, indexOffset, 1)[0] = args.access.index;
+    } else {
+      new Uint32Array(hostBuffer, indexOffset, 1)[0] = args.access.index;
+    }
+  }
+
+  runVectorComputeAndVerify(t, {
+    wgsl,
+    hostBuffer,
+    addressSpace: args.addressSpace,
+    elementType: args.elementType,
+    expectedValues,
+  });
+}
+
+/**
+ * Parameters for running a full vector assignment test.
+ */
+interface RunVectorFullAssignmentArgs {
+  elementType: VectorElementType;
+  width: VectorWidth;
+  addressSpace: AddressSpace;
+  memoryView: MemoryView;
+  rhsSource: 'constructor' | 'variable' | 'uniform';
+}
+
+/**
+ * Builds, runs, and checks the output of a full vector assignment test (v = rhs).
+ *
+ * @param t The test fixture object.
+ * @param args The test configuration arguments.
+ */
+function runVectorFullAssignmentTest(t: GPUTest, args: RunVectorFullAssignmentArgs) {
+  if (args.elementType === 'f16') {
+    t.skipIfDeviceDoesNotHaveFeature('shader-f16');
+  }
+
+  let initialValues: number[];
+  let replacementValues: number[];
+
+  switch (args.elementType) {
+    case 'bool':
+      initialValues = [0, 0, 0, 0].slice(0, args.width);
+      replacementValues = [1, 0, 1, 1].slice(0, args.width);
+      break;
+    case 'i32':
+      initialValues = [-1, -2, -3, -4].slice(0, args.width);
+      replacementValues = [-10, -20, -30, -40].slice(0, args.width);
+      break;
+    case 'u32':
+      initialValues = [1, 2, 3, 4].slice(0, args.width);
+      replacementValues = [10, 20, 30, 40].slice(0, args.width);
+      break;
+    case 'f32':
+    case 'f16':
+      initialValues = [1.0, 2.0, 3.0, 4.0].slice(0, args.width);
+      replacementValues = [10.5, 20.5, 30.5, 40.5].slice(0, args.width);
+      break;
+  }
+
+  const storageElemType = args.elementType === 'bool' ? 'u32' : args.elementType;
+  const outputElemType = args.elementType === 'bool' ? 'u32' : args.elementType;
+  const vecType = `vec${args.width}<${args.elementType}>`;
+  const storageVecType = `vec${args.width}<${storageElemType}>`;
+
+  const varRef = args.addressSpace === 'storage' ? 'storage_buffer.v' : 'v';
+  const target = args.memoryView === 'ptr' ? '(*ptr)' : varRef;
+  const ptrDecl = args.memoryView === 'ptr' ? `let ptr = &${varRef};\n  ` : '';
+
+  const rhsLiterals =
+    args.elementType === 'bool'
+      ? replacementValues.map(v => (v ? 'true' : 'false')).join(', ')
+      : args.elementType === 'f16'
+      ? replacementValues.map(v => `${v}h`).join(', ')
+      : args.elementType === 'f32'
+      ? replacementValues.map(v => `${v}f`).join(', ')
+      : args.elementType === 'i32'
+      ? replacementValues.map(v => `${v}i`).join(', ')
+      : replacementValues.map(v => `${v}u`).join(', ');
+
+  let rhsDecl = '';
+  let rhsExpr = '';
+  if (args.rhsSource === 'constructor') {
+    rhsExpr = `${vecType}(${rhsLiterals})`;
+  } else if (args.rhsSource === 'variable') {
+    rhsDecl = `let rhs_val = ${vecType}(${rhsLiterals});\n  `;
+    rhsExpr = 'rhs_val';
+  } else {
+    rhsExpr =
+      args.elementType === 'bool'
+        ? `inputs.replacement_vector != vec${args.width}<u32>(0u)`
+        : `inputs.replacement_vector`;
+  }
+
+  const assignmentStatement = `${rhsDecl}${ptrDecl}${target} = ${rhsExpr};`;
+
+  const wgsl = `
+${args.elementType === 'f16' ? 'enable f16;' : ''}
+
+struct Inputs {
+  initial_vector : ${storageVecType},
+  replacement_vector : ${storageVecType},
+};
+
+@group(0) @binding(0) var<uniform> inputs : Inputs;
+
+struct Outputs {
+  data : array<${outputElemType}>,
+};
+
+@group(0) @binding(1) var<storage, read_write> outputs : Outputs;
+
+${
+  args.addressSpace === 'storage'
+    ? `struct Storage {
+  v : ${storageVecType},
+};
+@group(0) @binding(2) var<storage, read_write> storage_buffer : Storage;`
+    : ''
+}
+${args.addressSpace === 'private' ? `var<private> v : ${vecType};` : ''}
+${args.addressSpace === 'workgroup' ? `var<workgroup> v : ${vecType};` : ''}
+
+@compute @workgroup_size(1)
+fn main() {
+  ${args.addressSpace === 'function' ? `var v : ${vecType};` : ''}
+  ${
+    args.addressSpace === 'storage'
+      ? `storage_buffer.v = inputs.initial_vector;`
+      : args.elementType === 'bool'
+      ? `${varRef} = inputs.initial_vector != vec${args.width}<u32>(0u);`
+      : `${varRef} = inputs.initial_vector;`
+  }
+
+  ${assignmentStatement}
+
+  for (var i = 0u; i < ${args.width}u; i++) {
+    outputs.data[i] = ${args.elementType === 'bool' ? `u32(${varRef}[i])` : `${varRef}[i]`};
+  }
+}
+`;
+
+  // Prepare input buffer data matching WGSL struct natural alignment.
+  const hostBuffer = new ArrayBuffer(64);
+  const elemBytes = args.elementType === 'f16' ? 2 : 4;
+  const vecAlign = (args.width === 2 ? 2 : 4) * elemBytes;
+  const replacementOffset = align(args.width * elemBytes, vecAlign);
+
+  if (args.elementType === 'f16') {
+    const f16Init = new Float16Array(hostBuffer, 0, args.width);
+    const f16Replacement = new Float16Array(hostBuffer, replacementOffset, args.width);
+    for (let i = 0; i < args.width; i++) {
+      f16Init[i] = initialValues[i];
+      f16Replacement[i] = replacementValues[i];
+    }
+  } else if (args.elementType === 'f32') {
+    const f32Init = new Float32Array(hostBuffer, 0, args.width);
+    const f32Replacement = new Float32Array(hostBuffer, replacementOffset, args.width);
+    for (let i = 0; i < args.width; i++) {
+      f32Init[i] = initialValues[i];
+      f32Replacement[i] = replacementValues[i];
+    }
+  } else if (args.elementType === 'i32') {
+    const i32Init = new Int32Array(hostBuffer, 0, args.width);
+    const i32Replacement = new Int32Array(hostBuffer, replacementOffset, args.width);
+    for (let i = 0; i < args.width; i++) {
+      i32Init[i] = initialValues[i];
+      i32Replacement[i] = replacementValues[i];
+    }
+  } else {
+    const u32Init = new Uint32Array(hostBuffer, 0, args.width);
+    const u32Replacement = new Uint32Array(hostBuffer, replacementOffset, args.width);
+    for (let i = 0; i < args.width; i++) {
+      u32Init[i] = initialValues[i];
+      u32Replacement[i] = replacementValues[i];
+    }
+  }
+
+  runVectorComputeAndVerify(t, {
+    wgsl,
+    hostBuffer,
+    addressSpace: args.addressSpace,
+    elementType: args.elementType,
+    expectedValues: replacementValues,
+  });
+}
+
+g.test('vector_component')
+  .desc('Tests writing to a vector component via component accessor (e.g. v.x = val).')
+  .params(u =>
+    u
+      .combine('elementType', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+      .combine('width', [2, 3, 4] as const)
+      .beginSubcases()
+      .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+      .filter(t => t.address_space !== 'storage' || t.elementType !== 'bool')
+      .combine('memory_view', ['ref', 'ptr'] as const)
+      .combine('component_set', ['xyzw', 'rgba'] as const)
+      .expand('component', u => kComponentSets[u.component_set].slice(0, u.width))
+  )
+  .fn(t => {
+    runVectorElementWriteTest(t, {
+      elementType: t.params.elementType,
+      width: t.params.width,
+      addressSpace: t.params.address_space,
+      memoryView: t.params.memory_view,
+      access: {
+        kind: 'component',
+        component: t.params.component as ComponentName,
+      },
+    });
+  });
+
+g.test('vector_constant_index')
+  .desc('Tests writing to a vector element via constant index (e.g. v[0] = val).')
+  .params(u =>
+    u
+      .combine('elementType', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+      .combine('width', [2, 3, 4] as const)
+      .beginSubcases()
+      .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+      .filter(t => t.address_space !== 'storage' || t.elementType !== 'bool')
+      .combine('memory_view', ['ref', 'ptr'] as const)
+      .expand('index', u => [...Array(u.width).keys()])
+  )
+  .fn(t => {
+    runVectorElementWriteTest(t, {
+      elementType: t.params.elementType,
+      width: t.params.width,
+      addressSpace: t.params.address_space,
+      memoryView: t.params.memory_view,
+      access: {
+        kind: 'constant_index',
+        index: t.params.index,
+      },
+    });
+  });
+
+g.test('vector_dynamic_index')
+  .desc(
+    'Tests writing to a vector element via dynamic index loaded from uniform buffer (e.g. v[index] = val).'
+  )
+  .params(u =>
+    u
+      .combine('elementType', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+      .combine('width', [2, 3, 4] as const)
+      .combine('indexType', ['i32', 'u32'] as const)
+      .beginSubcases()
+      .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+      .filter(t => t.address_space !== 'storage' || t.elementType !== 'bool')
+      .combine('memory_view', ['ref', 'ptr'] as const)
+      .expand('index', u => [...Array(u.width).keys()])
+  )
+  .fn(t => {
+    runVectorElementWriteTest(t, {
+      elementType: t.params.elementType,
+      width: t.params.width,
+      addressSpace: t.params.address_space,
+      memoryView: t.params.memory_view,
+      access: {
+        kind: 'dynamic_index',
+        index: t.params.index,
+        indexType: t.params.indexType,
+      },
+    });
+  });
+
+g.test('vector_element_compound')
+  .desc('Tests compound assignment to a vector element (e.g. v.x += val, v[i] += val).')
+  .params(u =>
+    u
+      .combine('elementType', ['i32', 'u32', 'f32', 'f16'] as const)
+      .combine('width', [2, 3, 4] as const)
+      .combine('op', ['add', 'mul'] as const)
+      .beginSubcases()
+      .combine('access', ['component', 'constant_index', 'dynamic_index'] as const)
+      .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+      .combine('memory_view', ['ref', 'ptr'] as const)
+  )
+  .fn(t => {
+    const targetIdx = 1;
+    let access: RunVectorElementWriteArgs['access'];
+    if (t.params.access === 'component') {
+      access = { kind: 'component', component: 'y' };
+    } else if (t.params.access === 'constant_index') {
+      access = { kind: 'constant_index', index: targetIdx };
+    } else {
+      access = { kind: 'dynamic_index', index: targetIdx, indexType: 'u32' };
+    }
+
+    runVectorElementWriteTest(t, {
+      elementType: t.params.elementType,
+      width: t.params.width,
+      addressSpace: t.params.address_space,
+      memoryView: t.params.memory_view,
+      access,
+      compoundOp: kCompoundOps[t.params.op],
+    });
+  });
+
+g.test('vector_full_assignment')
+  .desc('Tests whole-vector assignment (e.g. v = rhs).')
+  .params(u =>
+    u
+      .combine('elementType', ['bool', 'i32', 'u32', 'f32', 'f16'] as const)
+      .combine('width', [2, 3, 4] as const)
+      .combine('rhsSource', ['constructor', 'variable', 'uniform'] as const)
+      .beginSubcases()
+      .combine('address_space', ['function', 'private', 'workgroup', 'storage'] as const)
+      .filter(t => t.address_space !== 'storage' || t.elementType !== 'bool')
+      .combine('memory_view', ['ref', 'ptr'] as const)
+  )
+  .fn(t => {
+    runVectorFullAssignmentTest(t, {
+      elementType: t.params.elementType,
+      width: t.params.width,
+      addressSpace: t.params.address_space,
+      memoryView: t.params.memory_view,
+      rhsSource: t.params.rhsSource,
+    });
+  });
+
+g.test('indexed_assignment_eval_order')
+  .desc(
+    'Tests that the lhs components and rhs of an indexed vector assignment are evaluated left-to-right.'
+  )
+  .fn(t => {
+    runFlowControlTest(t, f => ({
+      entrypoint: `
+  arr[0] = vec4u(1, 10, 1, 1);
+  ${f.expect_order(0)}
+  arr[foo()][bar()] = baz();
+  ${f.expect_order(4)}
+  if (all(arr[0] == vec4u(1, 99, 1, 1))) {
+    ${f.expect_order(5)}
+  } else {
+    ${f.expect_not_reached()}
+  }
+`,
+      extra: `
+var<private> arr : array<vec4u, 1>;
+fn foo() -> u32 {
+  ${f.expect_order(1)}
+  return 0;
+}
+fn bar() -> u32 {
+  ${f.expect_order(2)}
+  return 1;
+}
+fn baz() -> u32 {
+  ${f.expect_order(3)}
+  return 99;
+}
+`,
+    }));
+  });
+
+g.test('indexed_compound_assignment_eval_order')
+  .desc(
+    'Tests that compound assignment on a vector element evaluates left-to-right and evaluates the index expression only once.'
+  )
+  .fn(t => {
+    runFlowControlTest(t, f => ({
+      entrypoint: `
+  arr[0] = vec4u(1, 10, 1, 1);
+  ${f.expect_order(0)}
+  arr[foo()][bar()] += baz();
+  ${f.expect_order(4)}
+  if (all(arr[0] == vec4u(1, 15, 1, 1))) {
+    ${f.expect_order(5)}
+  } else {
+    ${f.expect_not_reached()}
+  }
+`,
+      extra: `
+var<private> arr : array<vec4u, 1>;
+fn foo() -> u32 {
+  ${f.expect_order(1)}
+  return 0;
+}
+fn bar() -> u32 {
+  ${f.expect_order(2)}
+  return 1;
+}
+fn baz() -> u32 {
+  ${f.expect_order(3)}
+  return 5;
+}
+`,
+    }));
+  });