tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

conversion.spec.ts (25087B)


      1 export const description = `Unit tests for conversion`;
      2 
      3 import { mergeParams } from '../common/internal/params_utils.js';
      4 import { makeTestGroup } from '../common/internal/test_group.js';
      5 import { keysOf } from '../common/util/data_tables.js';
      6 import { assert, objectEquals } from '../common/util/util.js';
      7 import { kValue } from '../webgpu/util/constants.js';
      8 import {
      9  bool,
     10  concreteTypeOf,
     11  f16Bits,
     12  f32,
     13  f32Bits,
     14  float16BitsToFloat32,
     15  float32ToFloat16Bits,
     16  float32ToFloatBits,
     17  floatBitsToNormalULPFromZero,
     18  floatBitsToNumber,
     19  i32,
     20  kFloat16Format,
     21  kFloat32Format,
     22  MatrixValue,
     23  numbersApproximatelyEqual,
     24  pack2x16float,
     25  pack2x16snorm,
     26  pack2x16unorm,
     27  pack4x8snorm,
     28  pack4x8unorm,
     29  packRGB9E5UFloat,
     30  ScalarValue,
     31  toMatrix,
     32  u32,
     33  unpackRGB9E5UFloat,
     34  vec2,
     35  vec3,
     36  vec4,
     37  stringToType,
     38  Type,
     39  VectorValue,
     40 } from '../webgpu/util/conversion.js';
     41 
     42 import { UnitTest } from './unit_test.js';
     43 
     44 export const g = makeTestGroup(UnitTest);
     45 
     46 const kFloat16BitsToNumberCases = [
     47  [0b0_01111_0000000000, 1],
     48  [0b0_00001_0000000000, 0.00006103515625],
     49  [0b0_01101_0101010101, 0.33325195],
     50  [0b0_11110_1111111111, 65504],
     51  [0b0_00000_0000000000, 0],
     52  [0b1_00000_0000000000, -0.0], // -0.0 compares as equal to 0.0
     53  [0b0_01110_0000000000, 0.5],
     54  [0b0_01100_1001100110, 0.1999512],
     55  [0b0_01111_0000000001, 1.00097656],
     56  [0b0_10101_1001000000, 100],
     57  [0b1_01100_1001100110, -0.1999512],
     58  [0b1_10101_1001000000, -100],
     59  [0b0_11111_1111111111, Number.NaN],
     60  [0b0_11111_0000000000, Number.POSITIVE_INFINITY],
     61  [0b1_11111_0000000000, Number.NEGATIVE_INFINITY],
     62 ];
     63 
     64 g.test('float16BitsToFloat32').fn(t => {
     65  for (const [bits, number] of [
     66    ...kFloat16BitsToNumberCases,
     67    [0b0_00000_1111111111, 0.00006104], // subnormal f16 input
     68    [0b1_00000_1111111111, -0.00006104],
     69  ]) {
     70    const actual = float16BitsToFloat32(bits);
     71    t.expect(
     72      // some loose check
     73      numbersApproximatelyEqual(actual, number, 0.00001),
     74      `for ${bits.toString(2)}, expected ${number}, got ${actual}`
     75    );
     76  }
     77 });
     78 
     79 g.test('float32ToFloat16Bits').fn(t => {
     80  for (const [bits, number] of [
     81    ...kFloat16BitsToNumberCases,
     82    [0b0_00000_0000000000, 0.00001], // input that becomes subnormal in f16 is rounded to 0
     83    [0b1_00000_0000000000, -0.00001], // and sign is preserved
     84  ]) {
     85    // some loose check
     86    const actual = float32ToFloat16Bits(number);
     87    t.expect(
     88      Math.abs(actual - bits) <= 1,
     89      `for ${number}, expected ${bits.toString(2)}, got ${actual.toString(2)}`
     90    );
     91  }
     92 });
     93 
     94 g.test('float32ToFloatBits_floatBitsToNumber')
     95  .paramsSubcasesOnly(u =>
     96    u
     97      .combine('signed', [0, 1] as const)
     98      .combine('exponentBits', [5, 8])
     99      .combine('mantissaBits', [10, 23])
    100  )
    101  .fn(t => {
    102    const { signed, exponentBits, mantissaBits } = t.params;
    103    const bias = (1 << (exponentBits - 1)) - 1;
    104 
    105    for (const [, value] of kFloat16BitsToNumberCases) {
    106      if (value < 0 && signed === 0) continue;
    107      const bits = float32ToFloatBits(value, signed, exponentBits, mantissaBits, bias);
    108      const reconstituted = floatBitsToNumber(bits, { signed, exponentBits, mantissaBits, bias });
    109      t.expect(
    110        numbersApproximatelyEqual(reconstituted, value, 0.0000001),
    111        `${reconstituted} vs ${value}`
    112      );
    113    }
    114  });
    115 
    116 g.test('floatBitsToULPFromZero,16').fn(t => {
    117  const test = (bits: number, ulpFromZero: number) =>
    118    t.expect(floatBitsToNormalULPFromZero(bits, kFloat16Format) === ulpFromZero, bits.toString(2));
    119  // Zero
    120  test(0b0_00000_0000000000, 0);
    121  test(0b1_00000_0000000000, 0);
    122  // Subnormal
    123  test(0b0_00000_0000000001, 0);
    124  test(0b1_00000_0000000001, 0);
    125  test(0b0_00000_1111111111, 0);
    126  test(0b1_00000_1111111111, 0);
    127  // Normal
    128  test(0b0_00001_0000000000, 1); // 0 + 1ULP
    129  test(0b1_00001_0000000000, -1); // 0 - 1ULP
    130  test(0b0_00001_0000000001, 2); // 0 + 2ULP
    131  test(0b1_00001_0000000001, -2); // 0 - 2ULP
    132  test(0b0_01110_0000000000, 0b01101_0000000001); // 0.5
    133  test(0b1_01110_0000000000, -0b01101_0000000001); // -0.5
    134  test(0b0_01110_1111111110, 0b01101_1111111111); // 1.0 - 2ULP
    135  test(0b1_01110_1111111110, -0b01101_1111111111); // -(1.0 - 2ULP)
    136  test(0b0_01110_1111111111, 0b01110_0000000000); // 1.0 - 1ULP
    137  test(0b1_01110_1111111111, -0b01110_0000000000); // -(1.0 - 1ULP)
    138  test(0b0_01111_0000000000, 0b01110_0000000001); // 1.0
    139  test(0b1_01111_0000000000, -0b01110_0000000001); // -1.0
    140  test(0b0_01111_0000000001, 0b01110_0000000010); // 1.0 + 1ULP
    141  test(0b1_01111_0000000001, -0b01110_0000000010); // -(1.0 + 1ULP)
    142  test(0b0_10000_0000000000, 0b01111_0000000001); // 2.0
    143  test(0b1_10000_0000000000, -0b01111_0000000001); // -2.0
    144 
    145  const testThrows = (b: number) =>
    146    t.shouldThrow('Error', () => floatBitsToNormalULPFromZero(b, kFloat16Format));
    147  // Infinity
    148  testThrows(0b0_11111_0000000000);
    149  testThrows(0b1_11111_0000000000);
    150  // NaN
    151  testThrows(0b0_11111_1111111111);
    152  testThrows(0b1_11111_1111111111);
    153 });
    154 
    155 g.test('floatBitsToULPFromZero,32').fn(t => {
    156  const test = (bits: number, ulpFromZero: number) =>
    157    t.expect(floatBitsToNormalULPFromZero(bits, kFloat32Format) === ulpFromZero, bits.toString(2));
    158  // Zero
    159  test(0b0_00000000_00000000000000000000000, 0);
    160  test(0b1_00000000_00000000000000000000000, 0);
    161  // Subnormal
    162  test(0b0_00000000_00000000000000000000001, 0);
    163  test(0b1_00000000_00000000000000000000001, 0);
    164  test(0b0_00000000_11111111111111111111111, 0);
    165  test(0b1_00000000_11111111111111111111111, 0);
    166  // Normal
    167  test(0b0_00000001_00000000000000000000000, 1); // 0 + 1ULP
    168  test(0b1_00000001_00000000000000000000000, -1); // 0 - 1ULP
    169  test(0b0_00000001_00000000000000000000001, 2); // 0 + 2ULP
    170  test(0b1_00000001_00000000000000000000001, -2); // 0 - 2ULP
    171  test(0b0_01111110_00000000000000000000000, 0b01111101_00000000000000000000001); // 0.5
    172  test(0b1_01111110_00000000000000000000000, -0b01111101_00000000000000000000001); // -0.5
    173  test(0b0_01111110_11111111111111111111110, 0b01111101_11111111111111111111111); // 1.0 - 2ULP
    174  test(0b1_01111110_11111111111111111111110, -0b01111101_11111111111111111111111); // -(1.0 - 2ULP)
    175  test(0b0_01111110_11111111111111111111111, 0b01111110_00000000000000000000000); // 1.0 - 1ULP
    176  test(0b1_01111110_11111111111111111111111, -0b01111110_00000000000000000000000); // -(1.0 - 1ULP)
    177  test(0b0_01111111_00000000000000000000000, 0b01111110_00000000000000000000001); // 1.0
    178  test(0b1_01111111_00000000000000000000000, -0b01111110_00000000000000000000001); // -1.0
    179  test(0b0_01111111_00000000000000000000001, 0b01111110_00000000000000000000010); // 1.0 + 1ULP
    180  test(0b1_01111111_00000000000000000000001, -0b01111110_00000000000000000000010); // -(1.0 + 1ULP)
    181  test(0b0_11110000_00000000000000000000000, 0b11101111_00000000000000000000001); // 2.0
    182  test(0b1_11110000_00000000000000000000000, -0b11101111_00000000000000000000001); // -2.0
    183 
    184  const testThrows = (b: number) =>
    185    t.shouldThrow('Error', () => floatBitsToNormalULPFromZero(b, kFloat32Format));
    186  // Infinity
    187  testThrows(0b0_11111111_00000000000000000000000);
    188  testThrows(0b1_11111111_00000000000000000000000);
    189  // NaN
    190  testThrows(0b0_11111111_11111111111111111111111);
    191  testThrows(0b0_11111111_00000000000000000000001);
    192  testThrows(0b1_11111111_11111111111111111111111);
    193  testThrows(0b1_11111111_00000000000000000000001);
    194 });
    195 
    196 g.test('scalarWGSL').fn(t => {
    197  const cases: Array<[ScalarValue, string]> = [
    198    [f32(0.0), '0.0f'],
    199    // The number -0.0 can be remapped to 0.0 when stored in a Scalar
    200    // object. It is not possible to guarantee that '-0.0f' will
    201    // be emitted. So the WGSL scalar value printing does not try
    202    // to handle this case.
    203    [f32(-0.0), '0.0f'], // -0.0 can be remapped to 0.0
    204    [f32(1.0), '1.0f'],
    205    [f32(-1.0), '-1.0f'],
    206    [f32Bits(0x70000000), '1.5845632502852868e+29f'],
    207    [f32Bits(0xf0000000), '-1.5845632502852868e+29f'],
    208    [f16Bits(0), '0.0h'],
    209    [f16Bits(0x3c00), '1.0h'],
    210    [f16Bits(0xbc00), '-1.0h'],
    211    [u32(0), '0u'],
    212    [u32(1), '1u'],
    213    [u32(2000000000), '2000000000u'],
    214    [u32(-1), '4294967295u'],
    215    [i32(0), 'i32(0)'],
    216    [i32(1), 'i32(1)'],
    217    [i32(-1), 'i32(-1)'],
    218    [bool(true), 'true'],
    219    [bool(false), 'false'],
    220  ];
    221  for (const [value, expect] of cases) {
    222    const got = value.wgsl();
    223    t.expect(
    224      got === expect,
    225      `[value: ${value.value}, type: ${value.type}]
    226 got:    ${got}
    227 expect: ${expect}`
    228    );
    229  }
    230 });
    231 
    232 g.test('vectorWGSL').fn(t => {
    233  const cases: Array<[VectorValue, string]> = [
    234    [vec2(f32(42.0), f32(24.0)), 'vec2(42.0f, 24.0f)'],
    235    [vec2(f16Bits(0x5140), f16Bits(0x4e00)), 'vec2(42.0h, 24.0h)'],
    236    [vec2(u32(42), u32(24)), 'vec2(42u, 24u)'],
    237    [vec2(i32(42), i32(24)), 'vec2(i32(42), i32(24))'],
    238    [vec2(bool(false), bool(true)), 'vec2(false, true)'],
    239 
    240    [vec3(f32(0.0), f32(1.0), f32(-1.0)), 'vec3(0.0f, 1.0f, -1.0f)'],
    241    [vec3(f16Bits(0), f16Bits(0x3c00), f16Bits(0xbc00)), 'vec3(0.0h, 1.0h, -1.0h)'],
    242    [vec3(u32(0), u32(1), u32(-1)), 'vec3(0u, 1u, 4294967295u)'],
    243    [vec3(i32(0), i32(1), i32(-1)), 'vec3(i32(0), i32(1), i32(-1))'],
    244    [vec3(bool(true), bool(false), bool(true)), 'vec3(true, false, true)'],
    245 
    246    [vec4(f32(1.0), f32(-2.0), f32(4.0), f32(-8.0)), 'vec4(1.0f, -2.0f, 4.0f, -8.0f)'],
    247    [
    248      vec4(f16Bits(0xbc00), f16Bits(0x4000), f16Bits(0xc400), f16Bits(0x4800)),
    249      'vec4(-1.0h, 2.0h, -4.0h, 8.0h)',
    250    ],
    251    [vec4(u32(1), u32(-2), u32(4), u32(-8)), 'vec4(1u, 4294967294u, 4u, 4294967288u)'],
    252    [vec4(i32(1), i32(-2), i32(4), i32(-8)), 'vec4(i32(1), i32(-2), i32(4), i32(-8))'],
    253    [vec4(bool(false), bool(true), bool(true), bool(false)), 'vec4(false, true, true, false)'],
    254  ];
    255  for (const [value, expect] of cases) {
    256    const got = value.wgsl();
    257    t.expect(
    258      got === expect,
    259      `[values: ${value.elements}, type: ${value.type}]
    260 got:    ${got}
    261 expect: ${expect}`
    262    );
    263  }
    264 });
    265 
    266 g.test('matrixWGSL').fn(t => {
    267  const cases: Array<[MatrixValue, string]> = [
    268    [
    269      toMatrix(
    270        [
    271          [0.0, 1.0],
    272          [2.0, 3.0],
    273        ],
    274        f32
    275      ),
    276      'mat2x2(0.0f, 1.0f, 2.0f, 3.0f)',
    277    ],
    278    [
    279      toMatrix(
    280        [
    281          [0.0, 1.0, 2.0],
    282          [3.0, 4.0, 5.0],
    283        ],
    284        f32
    285      ),
    286      'mat2x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f)',
    287    ],
    288    [
    289      toMatrix(
    290        [
    291          [0.0, 1.0, 2.0, 3.0],
    292          [4.0, 5.0, 6.0, 7.0],
    293        ],
    294        f32
    295      ),
    296      'mat2x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)',
    297    ],
    298    [
    299      toMatrix(
    300        [
    301          [0.0, 1.0],
    302          [2.0, 3.0],
    303          [4.0, 5.0],
    304        ],
    305        f32
    306      ),
    307      'mat3x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f)',
    308    ],
    309    [
    310      toMatrix(
    311        [
    312          [0.0, 1.0, 2.0],
    313          [3.0, 4.0, 5.0],
    314          [6.0, 7.0, 8.0],
    315        ],
    316        f32
    317      ),
    318      'mat3x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f)',
    319    ],
    320    [
    321      toMatrix(
    322        [
    323          [0.0, 1.0, 2.0, 3.0],
    324          [4.0, 5.0, 6.0, 7.0],
    325          [8.0, 9.0, 10.0, 11.0],
    326        ],
    327        f32
    328      ),
    329      'mat3x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f)',
    330    ],
    331    [
    332      toMatrix(
    333        [
    334          [0.0, 1.0],
    335          [2.0, 3.0],
    336          [4.0, 5.0],
    337          [6.0, 7.0],
    338        ],
    339        f32
    340      ),
    341      'mat4x2(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f)',
    342    ],
    343    [
    344      toMatrix(
    345        [
    346          [0.0, 1.0, 2.0],
    347          [3.0, 4.0, 5.0],
    348          [6.0, 7.0, 8.0],
    349          [9.0, 10.0, 11.0],
    350        ],
    351        f32
    352      ),
    353      'mat4x3(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f)',
    354    ],
    355    [
    356      toMatrix(
    357        [
    358          [0.0, 1.0, 2.0, 3.0],
    359          [4.0, 5.0, 6.0, 7.0],
    360          [8.0, 9.0, 10.0, 11.0],
    361          [12.0, 13.0, 14.0, 15.0],
    362        ],
    363        f32
    364      ),
    365      'mat4x4(0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f)',
    366    ],
    367  ];
    368  for (const [value, expect] of cases) {
    369    const got = value.wgsl();
    370    t.expect(
    371      got === expect,
    372      `[values: ${value.elements}, type: ${value.type}]
    373 got:    ${got}
    374 expect: ${expect}`
    375    );
    376  }
    377 });
    378 
    379 g.test('constructorMatrix')
    380  .params(u =>
    381    u
    382      .combine('cols', [2, 3, 4] as const)
    383      .combine('rows', [2, 3, 4] as const)
    384      .combine('type', ['f32'] as const)
    385  )
    386  .fn(t => {
    387    const cols = t.params.cols;
    388    const rows = t.params.rows;
    389    const type = t.params.type;
    390    const scalar_builder = type === 'f32' ? f32 : undefined;
    391    assert(scalar_builder !== undefined, `Unexpected type param '${type}' provided`);
    392 
    393    const elements = [...Array(cols).keys()].map(c => {
    394      return [...Array(rows).keys()].map(r => scalar_builder(c * cols + r));
    395    });
    396 
    397    const got = new MatrixValue(elements);
    398    const got_type = got.type;
    399    t.expect(
    400      got_type.cols === cols,
    401      `expected Matrix to have ${cols} columns, received ${got_type.cols} instead`
    402    );
    403    t.expect(
    404      got_type.rows === rows,
    405      `expected Matrix to have ${rows} columns, received ${got_type.rows} instead`
    406    );
    407    t.expect(
    408      got_type.elementType.kind === type,
    409      `expected Matrix to have ${type} elements, received ${got_type.elementType.kind} instead`
    410    );
    411    t.expect(
    412      objectEquals(got.elements, elements),
    413      `Matrix did not have expected elements (${JSON.stringify(elements)}), instead had (${
    414        got.elements
    415      })`
    416    );
    417  });
    418 
    419 g.test('pack2x16float')
    420  .paramsSimple([
    421    // f16 normals
    422    { inputs: [0, 0], result: [0x00000000, 0x80000000, 0x00008000, 0x80008000] },
    423    { inputs: [1, 0], result: [0x00003c00, 0x80003c00] },
    424    { inputs: [1, 1], result: [0x3c003c00] },
    425    { inputs: [-1, -1], result: [0xbc00bc00] },
    426    { inputs: [10, 1], result: [0x3c004900] },
    427    { inputs: [-10, 1], result: [0x3c00c900] },
    428 
    429    // f32 normal, but not f16 precise
    430    { inputs: [1.00000011920928955078125, 1], result: [0x3c003c00, 0x3c003c01] },
    431 
    432    // f32 subnormals
    433    // prettier-ignore
    434    { inputs: [kValue.f32.positive.subnormal.max, 1], result: [0x3c000000, 0x3c008000, 0x3c000001] },
    435    // prettier-ignore
    436    { inputs: [kValue.f32.negative.subnormal.min, 1], result: [0x3c008001, 0x3c000000, 0x3c008000] },
    437 
    438    // f16 subnormals
    439    // prettier-ignore
    440    { inputs: [kValue.f16.positive.subnormal.max, 1], result: [0x3c0003ff, 0x3c000000, 0x3c008000] },
    441    // prettier-ignore
    442    { inputs: [kValue.f16.negative.subnormal.min, 1], result: [0x03c0083ff, 0x3c000000, 0x3c008000] },
    443 
    444    // f16 out of bounds
    445    { inputs: [kValue.f16.positive.max + 1, 1], result: [undefined] },
    446    { inputs: [kValue.f16.negative.min - 1, 1], result: [undefined] },
    447    { inputs: [1, kValue.f16.positive.max + 1], result: [undefined] },
    448    { inputs: [1, kValue.f16.negative.min - 1], result: [undefined] },
    449  ] as const)
    450  .fn(test => {
    451    const toString = (data: readonly (undefined | number)[]): String[] => {
    452      return data.map(d => (d !== undefined ? u32(d).toString() : 'undefined'));
    453    };
    454 
    455    const inputs = test.params.inputs;
    456    const got = pack2x16float(inputs[0], inputs[1]);
    457    const expect = test.params.result;
    458 
    459    const got_str = toString(got);
    460    const expect_str = toString(expect);
    461 
    462    // Using strings of the outputs, so they can be easily sorted, since order of the results doesn't matter.
    463    test.expect(
    464      objectEquals(got_str.sort(), expect_str.sort()),
    465      `pack2x16float(${inputs}) returned [${got_str}]. Expected [${expect_str}]`
    466    );
    467  });
    468 
    469 g.test('pack2x16snorm')
    470  .paramsSimple([
    471    // Normals
    472    { inputs: [0, 0], result: 0x00000000 },
    473    { inputs: [1, 0], result: 0x00007fff },
    474    { inputs: [0, 1], result: 0x7fff0000 },
    475    { inputs: [1, 1], result: 0x7fff7fff },
    476    { inputs: [-1, -1], result: 0x80018001 },
    477    { inputs: [10, 10], result: 0x7fff7fff },
    478    { inputs: [-10, -10], result: 0x80018001 },
    479    { inputs: [0.1, 0.1], result: 0x0ccd0ccd },
    480    { inputs: [-0.1, -0.1], result: 0xf333f333 },
    481    { inputs: [0.5, 0.5], result: 0x40004000 },
    482    { inputs: [-0.5, -0.5], result: 0xc001c001 },
    483    { inputs: [0.1, 0.5], result: 0x40000ccd },
    484    { inputs: [-0.1, -0.5], result: 0xc001f333 },
    485 
    486    // Subnormals
    487    { inputs: [kValue.f32.positive.subnormal.max, 1], result: 0x7fff0000 },
    488    { inputs: [kValue.f32.negative.subnormal.min, 1], result: 0x7fff0000 },
    489  ] as const)
    490  .fn(test => {
    491    const inputs = test.params.inputs;
    492    const got = pack2x16snorm(inputs[0], inputs[1]);
    493    const expect = test.params.result;
    494 
    495    test.expect(got === expect, `pack2x16snorm(${inputs}) returned ${got}. Expected ${expect}`);
    496  });
    497 
    498 g.test('pack2x16unorm')
    499  .paramsSimple([
    500    // Normals
    501    { inputs: [0, 0], result: 0x00000000 },
    502    { inputs: [1, 0], result: 0x0000ffff },
    503    { inputs: [0, 1], result: 0xffff0000 },
    504    { inputs: [1, 1], result: 0xffffffff },
    505    { inputs: [-1, -1], result: 0x00000000 },
    506    { inputs: [0.1, 0.1], result: 0x199a199a },
    507    { inputs: [0.5, 0.5], result: 0x80008000 },
    508    { inputs: [0.1, 0.5], result: 0x8000199a },
    509    { inputs: [10, 10], result: 0xffffffff },
    510 
    511    // Subnormals
    512    { inputs: [kValue.f32.positive.subnormal.max, 1], result: 0xffff0000 },
    513  ] as const)
    514  .fn(test => {
    515    const inputs = test.params.inputs;
    516    const got = pack2x16unorm(inputs[0], inputs[1]);
    517    const expect = test.params.result;
    518 
    519    test.expect(got === expect, `pack2x16unorm(${inputs}) returned ${got}. Expected ${expect}`);
    520  });
    521 
    522 g.test('pack4x8snorm')
    523  .paramsSimple([
    524    // Normals
    525    { inputs: [0, 0, 0, 0], result: 0x00000000 },
    526    { inputs: [1, 0, 0, 0], result: 0x0000007f },
    527    { inputs: [0, 1, 0, 0], result: 0x00007f00 },
    528    { inputs: [0, 0, 1, 0], result: 0x007f0000 },
    529    { inputs: [0, 0, 0, 1], result: 0x7f000000 },
    530    { inputs: [1, 1, 1, 1], result: 0x7f7f7f7f },
    531    { inputs: [10, 10, 10, 10], result: 0x7f7f7f7f },
    532    { inputs: [-1, 0, 0, 0], result: 0x00000081 },
    533    { inputs: [0, -1, 0, 0], result: 0x00008100 },
    534    { inputs: [0, 0, -1, 0], result: 0x00810000 },
    535    { inputs: [0, 0, 0, -1], result: 0x81000000 },
    536    { inputs: [-1, -1, -1, -1], result: 0x81818181 },
    537    { inputs: [-10, -10, -10, -10], result: 0x81818181 },
    538    { inputs: [0.1, 0.1, 0.1, 0.1], result: 0x0d0d0d0d },
    539    { inputs: [-0.1, -0.1, -0.1, -0.1], result: 0xf3f3f3f3 },
    540    { inputs: [0.1, -0.1, 0.1, -0.1], result: 0xf30df30d },
    541    { inputs: [0.5, 0.5, 0.5, 0.5], result: 0x40404040 },
    542    { inputs: [-0.5, -0.5, -0.5, -0.5], result: 0xc1c1c1c1 },
    543    { inputs: [-0.5, 0.5, -0.5, 0.5], result: 0x40c140c1 },
    544    { inputs: [0.1, 0.5, 0.1, 0.5], result: 0x400d400d },
    545    { inputs: [-0.1, -0.5, -0.1, -0.5], result: 0xc1f3c1f3 },
    546 
    547    // Subnormals
    548    { inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0x7f7f7f00 },
    549    { inputs: [kValue.f32.negative.subnormal.min, 1, 1, 1], result: 0x7f7f7f00 },
    550  ] as const)
    551  .fn(test => {
    552    const inputs = test.params.inputs;
    553    const got = pack4x8snorm(inputs[0], inputs[1], inputs[2], inputs[3]);
    554    const expect = test.params.result;
    555 
    556    test.expect(got === expect, `pack4x8snorm(${inputs}) returned ${u32(got)}. Expected ${expect}`);
    557  });
    558 
    559 g.test('pack4x8unorm')
    560  .paramsSimple([
    561    // Normals
    562    { inputs: [0, 0, 0, 0], result: 0x00000000 },
    563    { inputs: [1, 0, 0, 0], result: 0x000000ff },
    564    { inputs: [0, 1, 0, 0], result: 0x0000ff00 },
    565    { inputs: [0, 0, 1, 0], result: 0x00ff0000 },
    566    { inputs: [0, 0, 0, 1], result: 0xff000000 },
    567    { inputs: [1, 1, 1, 1], result: 0xffffffff },
    568    { inputs: [10, 10, 10, 10], result: 0xffffffff },
    569    { inputs: [-1, -1, -1, -1], result: 0x00000000 },
    570    { inputs: [-10, -10, -10, -10], result: 0x00000000 },
    571    { inputs: [0.1, 0.1, 0.1, 0.1], result: 0x1a1a1a1a },
    572    { inputs: [0.5, 0.5, 0.5, 0.5], result: 0x80808080 },
    573    { inputs: [0.1, 0.5, 0.1, 0.5], result: 0x801a801a },
    574 
    575    // Subnormals
    576    { inputs: [kValue.f32.positive.subnormal.max, 1, 1, 1], result: 0xffffff00 },
    577  ] as const)
    578  .fn(test => {
    579    const inputs = test.params.inputs;
    580    const got = pack4x8unorm(inputs[0], inputs[1], inputs[2], inputs[3]);
    581    const expect = test.params.result;
    582 
    583    test.expect(got === expect, `pack4x8unorm(${inputs}) returned ${got}. Expected ${expect}`);
    584  });
    585 
    586 const kRGB9E5UFloatCommonData = {
    587  zero: /*        */ { encoded: 0b00000_000000000_000000000_000000000, rgb: [0, 0, 0] },
    588  max: /*         */ { encoded: 0b11111_111111111_111111111_111111111, rgb: [65408, 65408, 65408] },
    589  r1: /*          */ { encoded: 0b10000_000000000_000000000_100000000, rgb: [1, 0, 0] },
    590  r2: /*          */ { encoded: 0b10001_000000000_000000000_100000000, rgb: [2, 0, 0] },
    591  g1: /*          */ { encoded: 0b10000_000000000_100000000_000000000, rgb: [0, 1, 0] },
    592  g2: /*          */ { encoded: 0b10001_000000000_100000000_000000000, rgb: [0, 2, 0] },
    593  b1: /*          */ { encoded: 0b10000_100000000_000000000_000000000, rgb: [0, 0, 1] },
    594  b2: /*          */ { encoded: 0b10001_100000000_000000000_000000000, rgb: [0, 0, 2] },
    595  r1_g1_b1: /*    */ { encoded: 0b10000_100000000_100000000_100000000, rgb: [1, 1, 1] },
    596  r1_g2_b1: /*    */ { encoded: 0b10001_010000000_100000000_010000000, rgb: [1, 2, 1] },
    597  r4_g8_b2: /*    */ { encoded: 0b10011_001000000_100000000_010000000, rgb: [4, 8, 2] },
    598  r1_g2_b3: /*    */ { encoded: 0b10001_110000000_100000000_010000000, rgb: [1, 2, 3] },
    599  r128_g3968_b65408: { encoded: 0b11111_111111111_000011111_000000001, rgb: [128, 3968, 65408] },
    600  r128_g1984_b30016: { encoded: 0b11110_111010101_000011111_000000010, rgb: [128, 1984, 30016] },
    601  r_5_g_25_b_8: /**/ { encoded: 0b10011_100000000_000001000_000010000, rgb: [0.5, 0.25, 8] },
    602 };
    603 
    604 const kPackRGB9E5UFloatData = mergeParams(kRGB9E5UFloatCommonData, {
    605  clamp_max: /*   */ { encoded: 0b11111_111111111_111111111_111111111, rgb: [1e7, 1e10, 1e50] },
    606  subnormals: /*  */ { encoded: 0b00000_000000000_000000000_000000000, rgb: [1e-10, 1e-20, 1e-30] },
    607  r57423_g54_b3478: { encoded: 0b11111_000011011_000000000_111000001, rgb: [57423, 54, 3478] },
    608  r6852_g3571_b2356: { encoded: 0b11100_010010011_011011111_110101100, rgb: [6852, 3571, 2356] },
    609  r68312_g12_b8123: { encoded: 0b11111_000111111_000000000_111111111, rgb: [68312, 12, 8123] },
    610  r7321_g846_b32: { encoded: 0b11100_000000010_000110101_111001010, rgb: [7321, 846, 32] },
    611 });
    612 
    613 function bits5_9_9_9(x: number) {
    614  const s = (x >>> 0).toString(2).padStart(32, '0');
    615  return `${s.slice(0, 5)}_${s.slice(5, 14)}_${s.slice(14, 23)}_${s.slice(23, 32)}`;
    616 }
    617 
    618 g.test('packRGB9E5UFloat')
    619  .params(u => u.combine('case', keysOf(kPackRGB9E5UFloatData)))
    620  .fn(test => {
    621    const c = kPackRGB9E5UFloatData[test.params.case];
    622    const got = packRGB9E5UFloat(c.rgb[0], c.rgb[1], c.rgb[2]);
    623    const expect = c.encoded;
    624 
    625    test.expect(
    626      got === expect,
    627      `packRGB9E5UFloat(${c.rgb}) returned ${bits5_9_9_9(got)}. Expected ${bits5_9_9_9(expect)}`
    628    );
    629  });
    630 
    631 g.test('unpackRGB9E5UFloat')
    632  .params(u => u.combine('case', keysOf(kRGB9E5UFloatCommonData)))
    633  .fn(test => {
    634    const c = kRGB9E5UFloatCommonData[test.params.case];
    635    const got = unpackRGB9E5UFloat(c.encoded);
    636    const expect = c.rgb;
    637 
    638    test.expect(
    639      got.R === expect[0] && got.G === expect[1] && got.B === expect[2],
    640      `unpackRGB9E5UFloat(${bits5_9_9_9(c.encoded)} ` +
    641        `returned ${got.R},${got.G},${got.B}. Expected ${expect}`
    642    );
    643  });
    644 
    645 const kConcreteTypeOfNoAllowedListCases = {
    646  bool: Type.bool,
    647  i32: Type.i32,
    648  u32: Type.u32,
    649  f32: Type.f32,
    650  f16: Type.f16,
    651  abstractInt: Type.i32,
    652  abstractFloat: Type.f32,
    653  vec2b: Type.vec2b,
    654  vec2i: Type.vec2i,
    655  vec3u: Type.vec3u,
    656  vec4f: Type.vec4f,
    657  vec2h: Type.vec2h,
    658  vec3ai: Type.vec3i,
    659  vec4af: Type.vec4f,
    660  mat2x2f: Type.mat2x2f,
    661  mat3x4h: Type.mat3x4h,
    662 } as const;
    663 
    664 g.test('concreteTypeOf_noAllowedLiist')
    665  .params(u => u.combine('src', keysOf(kConcreteTypeOfNoAllowedListCases)))
    666  .fn(test => {
    667    const src = test.params.src;
    668    const dest = kConcreteTypeOfNoAllowedListCases[src];
    669    const got = concreteTypeOf(stringToType(src));
    670    test.expect(got === dest);
    671  });
    672 
    673 const kConcreteTypeOfAllowListCases = {
    674  af_f32: { type: Type.abstractFloat, allowed: [Type.f32], expect: Type.f32 },
    675  af_f16: { type: Type.abstractFloat, allowed: [Type.f16], expect: Type.f16 },
    676  af_all: {
    677    type: Type.abstractFloat,
    678    allowed: [Type.f16, Type.f32, Type.i32, Type.u32],
    679    expect: Type.f32,
    680  },
    681  ai_i32: { type: Type.abstractInt, allowed: [Type.i32], expect: Type.i32 },
    682  ai_u32: { type: Type.abstractInt, allowed: [Type.u32], expect: Type.u32 },
    683  ai_f32: { type: Type.abstractInt, allowed: [Type.f32], expect: Type.f32 },
    684  ai_f16: { type: Type.abstractInt, allowed: [Type.f16], expect: Type.f16 },
    685  ai_all: {
    686    type: Type.abstractInt,
    687    allowed: [Type.f16, Type.f32, Type.i32, Type.u32],
    688    expect: Type.i32,
    689  },
    690 } as const;
    691 
    692 g.test('concreteTypeOf_AllowedLiist')
    693  .params(u => u.combine('k', keysOf(kConcreteTypeOfAllowListCases)))
    694  .fn(test => {
    695    const { type, allowed, expect } = kConcreteTypeOfAllowListCases[test.params.k];
    696    const got = concreteTypeOf(type, allowed as [Type]);
    697    test.expect(got === expect);
    698  });