🏡 index : cold-air-networks/mirror-webgpu-cts.git

export const description = `
Tests for properties of the WebGPU memory model involving two memory locations.
Specifically, the acquire/release ordering provided by WebGPU's barriers can be used to disallow
weak behaviors in several classic memory model litmus tests.`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUTest } from '../../../gpu_test.js';

import {
  MemoryModelTestParams,
  MemoryModelTester,
  buildTestShader,
  MemoryType,
  TestType,
  buildResultShader,
  ResultType,
} from './memory_model_setup.js';

export const g = makeTestGroup(GPUTest);

// A reasonable parameter set, determined heuristically.
const memoryModelTestParams: MemoryModelTestParams = {
  workgroupSize: 256,
  testingWorkgroups: 739,
  maxWorkgroups: 885,
  shufflePct: 0,
  barrierPct: 0,
  memStressPct: 0,
  memStressIterations: 1024,
  memStressStoreFirstPct: 50,
  memStressStoreSecondPct: 50,
  preStressPct: 100,
  preStressIterations: 33,
  preStressStoreFirstPct: 0,
  preStressStoreSecondPct: 100,
  scratchMemorySize: 1408,
  stressLineSize: 4,
  stressTargetLines: 11,
  stressStrategyBalancePct: 0,
  permuteFirst: 109,
  permuteSecond: 419,
  memStride: 2,
  aliasedMemory: false,
  numBehaviors: 4,
};

const workgroupMemoryMessagePassingTestCode = `
  atomicStore(&wg_test_locations[x_0], 1u);
  workgroupBarrier();
  atomicStore(&wg_test_locations[y_0], 1u);
  let r0 = atomicLoad(&wg_test_locations[y_1]);
  workgroupBarrier();
  let r1 = atomicLoad(&wg_test_locations[x_1]);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
`;

const storageMemoryMessagePassingTestCode = `
  atomicStore(&test_locations.value[x_0], 1u);
  storageBarrier();
  atomicStore(&test_locations.value[y_0], 1u);
  let r0 = atomicLoad(&test_locations.value[y_1]);
  storageBarrier();
  let r1 = atomicLoad(&test_locations.value[x_1]);
  atomicStore(&results.value[shuffled_workgroup * u32(workgroupXSize) + id_1].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * u32(workgroupXSize) + id_1].r1, r1);
`;

g.test('message_passing')
  .desc(
    `Checks whether two reads on one thread can observe two writes in another thread in a way
    that is inconsistent with sequential consistency. In the message passing litmus test, one
    thread writes the value 1 to some location x and then 1 to some location y. The second thread
    reads y and then x. If the second thread reads y == 1 and x == 0, then sequential consistency
    has not been respected. The acquire/release semantics of WebGPU's barrier functions should disallow
    this behavior within a workgroup.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryMessagePassingTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryMessagePassingTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((r0 == 0u && r1 == 0u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((r0 == 1u && r1 == 1u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((r0 == 0u && r1 == 1u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((r0 == 1u && r1 == 0u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });

const workgroupMemoryStoreTestCode = `
  atomicStore(&wg_test_locations[x_0], 2u);
  workgroupBarrier();
  atomicStore(&wg_test_locations[y_0], 1u);
  let r0 = atomicLoad(&wg_test_locations[y_1]);
  workgroupBarrier();
  atomicStore(&wg_test_locations[x_1], 1u);
  workgroupBarrier();
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
  atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
`;

const storageMemoryStoreTestCode = `
  atomicStore(&test_locations.value[x_0], 2u);
  storageBarrier();
  atomicStore(&test_locations.value[y_0], 1u);
  let r0 = atomicLoad(&test_locations.value[y_1]);
  storageBarrier();
  atomicStore(&test_locations.value[x_1], 1u);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;

g.test('store')
  .desc(
    `In the store litmus test, one thread writes 2 to some memory location x and then 1 to some memory location
     y. A second thread reads the value of y and then writes 1 to x. If the read on the second thread returns 1,
     but the value of x in memory after the test ends is 2, then there has been a re-ordering which is not allowed
     when using WebGPU's barriers.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryStoreTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryStoreTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((r0 == 1u && mem_x_0 == 1u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((r0 == 0u && mem_x_0 == 2u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((r0 == 0u && mem_x_0 == 1u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((r0 == 1u && mem_x_0 == 2u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });

const workgroupMemoryLoadBufferTestCode = `
  let r0 = atomicLoad(&wg_test_locations[y_0]);
  workgroupBarrier();
  atomicStore(&wg_test_locations[x_0], 1u);
  let r1 = atomicLoad(&wg_test_locations[x_1]);
  workgroupBarrier();
  atomicStore(&wg_test_locations[y_1], 1u);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
`;

const storageMemoryLoadBufferTestCode = `
  let r0 = atomicLoad(&test_locations.value[y_0]);
  storageBarrier();
  atomicStore(&test_locations.value[x_0], 1u);
  let r1 = atomicLoad(&test_locations.value[x_1]);
  storageBarrier();
  atomicStore(&test_locations.value[y_1], 1u);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
`;

g.test('load_buffer')
  .desc(
    `In the load buffer litmus test, one thread reads from memory location y and then writes 1 to memory location x.
     A second thread reads from x and then writes 1 to y. If both threads read the value 0, then the loads have been
     buffered or re-ordered, which is not allowed when used in conjunction with WebGPU's barriers.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryLoadBufferTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryLoadBufferTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((r0 == 1u && r1 == 0u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((r0 == 0u && r1 == 1u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((r0 == 0u && r1 == 0u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((r0 == 1u && r1 == 1u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });

const workgroupMemoryReadTestCode = `
  atomicStore(&wg_test_locations[x_0], 1u);
  workgroupBarrier();
  atomicExchange(&wg_test_locations[y_0], 1u);
  atomicExchange(&wg_test_locations[y_1], 2u);
  workgroupBarrier();
  let r0 = atomicLoad(&wg_test_locations[x_1]);
  workgroupBarrier();
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
  atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + y_1], atomicLoad(&wg_test_locations[y_1]));
`;

const storageMemoryReadTestCode = `
  atomicStore(&test_locations.value[x_0], 1u);
  storageBarrier();
  atomicExchange(&test_locations.value[y_0], 1u);
  atomicExchange(&test_locations.value[y_1], 2u);
  storageBarrier();
  let r0 = atomicLoad(&test_locations.value[x_1]);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r0, r0);
`;

g.test('read')
  .desc(
    `In the read litmus test, one thread writes 1 to memory location x and then 1 to memory location y. A second thread
     first writes 2 to y and then reads from x. If the value read by the second thread is 0 but the value in memory of y
     after the test completes is 2, then there has been some re-ordering of instructions disallowed when using WebGPU's
     barrier. Additionally, both writes to y are RMWs, so that the barrier forces the correct acquire/release memory ordering
     synchronization.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryReadTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryReadTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((r0 == 1u && mem_y_0 == 2u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((r0 == 0u && mem_y_0 == 1u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((r0 == 1u && mem_y_0 == 1u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((r0 == 0u && mem_y_0 == 2u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });

const workgroupMemoryStoreBufferTestCode = `
  atomicStore(&wg_test_locations[x_0], 1u);
  workgroupBarrier();
  let r0 = atomicAdd(&wg_test_locations[y_0], 0u);
  atomicExchange(&wg_test_locations[y_1], 1u);
  workgroupBarrier();
  let r1 = atomicLoad(&wg_test_locations[x_1]);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
`;

const storageMemoryStoreBufferTestCode = `
  atomicStore(&test_locations.value[x_0], 1u);
  storageBarrier();
  let r0 = atomicAdd(&test_locations.value[y_0], 0u);
  atomicExchange(&test_locations.value[y_1], 1u);
  storageBarrier();
  let r1 = atomicLoad(&test_locations.value[x_1]);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_0].r0, r0);
  atomicStore(&results.value[shuffled_workgroup * workgroupXSize + id_1].r1, r1);
`;

g.test('store_buffer')
  .desc(
    `In the store buffer litmus test, one thread writes 1 to memory location x and then reads from memory location
     y. A second thread writes 1 to y and then reads from x. If both reads return 0, then stores have been buffered
     or some other re-ordering has occurred that is disallowed by WebGPU's barriers. Additionally, both the read
     and store to y are RMWs to achieve the necessary synchronization across threads.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemoryStoreBufferTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemoryStoreBufferTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((r0 == 1u && r1 == 0u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((r0 == 0u && r1 == 1u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((r0 == 1u && r1 == 1u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((r0 == 0u && r1 == 0u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });

const workgroupMemory2P2WTestCode = `
  atomicStore(&wg_test_locations[x_0], 2u);
  workgroupBarrier();
  atomicExchange(&wg_test_locations[y_0], 1u);
  atomicExchange(&wg_test_locations[y_1], 2u);
  workgroupBarrier();
  atomicStore(&wg_test_locations[x_1], 1u);
  workgroupBarrier();
  atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + x_1], atomicLoad(&wg_test_locations[x_1]));
  atomicStore(&test_locations.value[shuffled_workgroup * workgroupXSize * stress_params.mem_stride * 2u + y_1], atomicLoad(&wg_test_locations[y_1]));
`;

const storageMemory2P2WTestCode = `
  atomicStore(&test_locations.value[x_0], 2u);
  storageBarrier();
  atomicExchange(&test_locations.value[y_0], 1u);
  atomicExchange(&test_locations.value[y_1], 2u);
  storageBarrier();
  atomicStore(&test_locations.value[x_1], 1u);
`;

g.test('2_plus_2_write')
  .desc(
    `In the 2+2 write litmus test, one thread stores 2 to memory location x and then 1 to memory location y.
     A second thread stores 2 to y and then 1 to x. If at the end of the test both memory locations are set to 2,
     then some disallowed re-ordering has occurred. Both writes to y are RMWs to achieve the required synchronization.
    `
  )
  .paramsSimple([
    { memType: MemoryType.AtomicWorkgroupClass, _testCode: workgroupMemory2P2WTestCode },
    { memType: MemoryType.AtomicStorageClass, _testCode: storageMemory2P2WTestCode },
  ])
  .fn(async t => {
    const testShader = buildTestShader(
      t.params._testCode,
      t.params.memType,
      TestType.IntraWorkgroup
    );
    const messagePassingResultShader = buildResultShader(
      `
      if ((mem_x_0 == 1u && mem_y_0 == 2u)) {
        atomicAdd(&test_results.seq0, 1u);
      } else if ((mem_x_0 == 2u && mem_y_0 == 1u)) {
        atomicAdd(&test_results.seq1, 1u);
      } else if ((mem_x_0 == 1u && mem_y_0 == 1u)) {
        atomicAdd(&test_results.interleaved, 1u);
      } else if ((mem_x_0 == 2u && mem_y_0 == 2u)) {
        atomicAdd(&test_results.weak, 1u);
      }
      `,
      TestType.IntraWorkgroup,
      ResultType.FourBehavior
    );
    const memModelTester = new MemoryModelTester(
      t,
      memoryModelTestParams,
      testShader,
      messagePassingResultShader
    );
    await memModelTester.run(40, 3);
  });