export const description = `
Validation tests for the bitwise shift binary expression operations
`;

import { makeTestGroup } from '../../../../../common/framework/test_group.js';
import { ShaderValidationTest } from '../../shader_validation_test.js';

export const g = makeTestGroup(ShaderValidationTest);

// Converts v to signed decimal number.
// Required because JS binary literals are always interpreted as unsigned numbers.
function signed(v: number): number {
  return new Int32Array([v])[0];
}

// Return vector form of size `size` of input value `v`, or `v` if size is undefined.
function vectorize(v: string, size: number | undefined): string {
  if (size !== undefined) {
    return `vec${size}(${v})`;
  }
  return v;
}

const kLeftShiftCases = [
  // rhs >= bitwidth fails
  { lhs: `0u`, rhs: `31u`, pass: true },
  { lhs: `0u`, rhs: `32u`, pass: false },
  { lhs: `0u`, rhs: `33u`, pass: false },
  { lhs: `0u`, rhs: `1000u`, pass: false },
  { lhs: `0u`, rhs: `0xFFFFFFFFu`, pass: false },

  { lhs: `0i`, rhs: `31u`, pass: true },
  { lhs: `0i`, rhs: `32u`, pass: false },
  { lhs: `0i`, rhs: `33u`, pass: false },
  { lhs: `0i`, rhs: `1000u`, pass: false },
  { lhs: `0i`, rhs: `0xFFFFFFFFu`, pass: false },

  // Signed overflow (sign change)
  { lhs: `${0b01000000000000000000000000000000}i`, rhs: `1u`, pass: false },
  { lhs: `${0b01111111111111111111111111111111}i`, rhs: `1u`, pass: false },
  { lhs: `${0b00000000000000000000000000000001}i`, rhs: `31u`, pass: false },
  // Same cases should pass if lhs is unsigned
  { lhs: `${0b01000000000000000000000000000000}u`, rhs: `1u`, pass: true },
  { lhs: `${0b01111111111111111111111111111111}u`, rhs: `1u`, pass: true },
  { lhs: `${0b00000000000000000000000000000001}u`, rhs: `31u`, pass: true },

  // Unsigned overflow
  { lhs: `${0b11000000000000000000000000000000}u`, rhs: `1u`, pass: false },
  { lhs: `${0b11111111111111111111111111111111}u`, rhs: `1u`, pass: false },
  { lhs: `${0b11111111111111111111111111111111}u`, rhs: `31u`, pass: false },
  // Same cases should pass if lhs is signed
  { lhs: `${signed(0b11000000000000000000000000000000)}i`, rhs: `1u`, pass: true },
  { lhs: `${signed(0b11111111111111111111111111111111)}i`, rhs: `1u`, pass: true },
  { lhs: `${signed(0b11111111111111111111111111111111)}i`, rhs: `31u`, pass: true },

  // Shift by negative is an error
  { lhs: `1`, rhs: `-1`, pass: false },
  { lhs: `1i`, rhs: `-1`, pass: false },
  { lhs: `1u`, rhs: `-1`, pass: false },
];

g.test('shift_left_concrete')
  .desc('Tests validation of binary left shift of concrete values')
  .params(u =>
    u
      .combine('case', kLeftShiftCases) //
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(t => {
    const lhs = t.params.case.lhs;
    const rhs = t.params.case.rhs;
    const vec_size = t.params.vectorize;

    const code = `
@compute @workgroup_size(1)
fn main() {
    const r = ${vectorize(lhs, vec_size)} << ${vectorize(rhs, vec_size)};
}
    `;
    t.expectCompileResult(t.params.case.pass, code);
  });

g.test('shift_left_vec_size_mismatch')
  .desc('Tests validation of binary left shift of vectors with mismatched sizes')
  .params(u =>
    u
      .combine('vectorize_lhs', [2, 3, 4]) //
      .combine('vectorize_rhs', [2, 3, 4])
  )
  .fn(t => {
    const lhs = `1`;
    const rhs = `1`;
    const lhs_vec_size = t.params.vectorize_lhs;
    const rhs_vec_size = t.params.vectorize_rhs;
    const code = `
@compute @workgroup_size(1)
fn main() {
    const r = ${vectorize(lhs, lhs_vec_size)} << ${vectorize(rhs, rhs_vec_size)};
}
    `;
    const pass = lhs_vec_size === rhs_vec_size;
    t.expectCompileResult(pass, code);
  });

const kRightShiftCases = [
  // rhs >= bitwidth fails
  { lhs: `0u`, rhs: `31u`, pass: true },
  { lhs: `0u`, rhs: `32u`, pass: false },
  { lhs: `0u`, rhs: `33u`, pass: false },
  { lhs: `0u`, rhs: `1000u`, pass: false },
  { lhs: `0u`, rhs: `0xFFFFFFFFu`, pass: false },

  { lhs: `0i`, rhs: `31u`, pass: true },
  { lhs: `0i`, rhs: `32u`, pass: false },
  { lhs: `0i`, rhs: `33u`, pass: false },
  { lhs: `0i`, rhs: `1000u`, pass: false },
  { lhs: `0i`, rhs: `0xFFFFFFFFu`, pass: false },

  // Shift by negative is an error
  { lhs: `1`, rhs: `-1`, pass: false },
  { lhs: `1i`, rhs: `-1`, pass: false },
  { lhs: `1u`, rhs: `-1`, pass: false },
];

g.test('shift_right_concrete')
  .desc('Tests validation of binary right shift of concrete values')
  .params(u =>
    u
      .combine('case', kRightShiftCases) //
      .combine('vectorize', [undefined, 2, 3, 4] as const)
  )
  .fn(t => {
    const lhs = t.params.case.lhs;
    const rhs = t.params.case.rhs;
    const vec_size = t.params.vectorize;

    const code = `
@compute @workgroup_size(1)
fn main() {
    const r = ${vectorize(lhs, vec_size)} >> ${vectorize(rhs, vec_size)};
}
    `;
    t.expectCompileResult(t.params.case.pass, code);
  });

g.test('shift_right_vec_size_mismatch')
  .desc('Tests validation of binary right shift of vectors with mismatched sizes')
  .params(u =>
    u
      .combine('vectorize_lhs', [2, 3, 4]) //
      .combine('vectorize_rhs', [2, 3, 4])
  )
  .fn(t => {
    const lhs = `1`;
    const rhs = `1`;
    const lhs_vec_size = t.params.vectorize_lhs;
    const rhs_vec_size = t.params.vectorize_rhs;
    const code = `
@compute @workgroup_size(1)
fn main() {
    const r = ${vectorize(lhs, lhs_vec_size)} >> ${vectorize(rhs, rhs_vec_size)};
}
    `;
    const pass = lhs_vec_size === rhs_vec_size;
    t.expectCompileResult(pass, code);
  });
