// META: title=test WebNN API tensor operations // META: global=window,worker // META: variant=?cpu // META: variant=?gpu // META: variant=?npu // META: script=../resources/utils_validation.js // META: script=../resources/utils.js // META: timeout=long 'use strict'; // Skip tests if WebNN is unimplemented. promise_setup(async () => { assert_implements(navigator.ml, 'missing navigator.ml'); }); // https://www.w3.org/TR/webnn/#api-mltensor const testContents = Uint32Array.from([0, 1, 2, 3, 4, 5, 6, 7]); let mlContext; let mlTensor; promise_setup(async () => { try { mlContext = await navigator.ml.createContext(contextOptions); } catch (e) { throw new AssertionError( `Unable to create context for ${variant} variant. ${e}`); } try { mlTensor = await mlContext.createTensor({ dataType: 'int32', shape: [2, 4], readable: true, writable: true, }); } catch (e) { throw new AssertionError( `Unable to create tensor for ${variant} variant. ${e}`); } mlContext.writeTensor(mlTensor, testContents); }); promise_test(async (t) => { const arrayBuffer = new ArrayBuffer(testContents.byteLength - 4); await promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, arrayBuffer)); }, `readTensor() with an ArrayBuffer that is too small should reject`); promise_test(async (t) => { const typedArray = new Uint32Array(testContents.length - 1); await promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, typedArray)); }, `readTensor() with a TypedArray that is too small should reject`); promise_test(async (t) => { const arrayBuffer = new ArrayBuffer(testContents.byteLength); const typedArray = new Uint32Array(arrayBuffer); arrayBuffer.transfer(); await promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, arrayBuffer)); await promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, typedArray)); }, `readTensor() with a detached ArrayBuffer should reject`); promise_test(async (t) => { const arrayBuffer = new ArrayBuffer(testContents.byteLength); const typedArray = new Uint32Array(arrayBuffer); const checks = Promise.all([ promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, arrayBuffer)), promise_rejects_js( t, TypeError, mlContext.readTensor(mlTensor, typedArray)), ]); arrayBuffer.transfer(); await checks; }, `Detaching an ArrayBuffer while readTensor() is in progress should reject`); promise_test(async () => { const arrayBuffer = new ArrayBuffer(testContents.byteLength); await mlContext.readTensor(mlTensor, arrayBuffer); assert_array_equals(new Uint32Array(arrayBuffer), testContents); }, `readTensor() with an ArrayBuffer`); if ('SharedArrayBuffer' in globalThis) { promise_test(async () => { const sharedArrayBuffer = new SharedArrayBuffer(testContents.byteLength); await mlContext.readTensor(mlTensor, sharedArrayBuffer); assert_array_equals(new Uint32Array(sharedArrayBuffer), testContents); }, `readTensor() with a SharedArrayBuffer`); promise_test(async () => { const sharedArrayBuffer = new SharedArrayBuffer(testContents.byteLength); const typedArray = new Uint32Array(sharedArrayBuffer); await mlContext.readTensor(mlTensor, typedArray); assert_array_equals(typedArray, testContents); }, `readTensor() with a typeArray from a SharedArrayBuffer`); } promise_test(async () => { // Create a slightly larger ArrayBuffer and set up the TypedArray at an // offset to make sure the MLTensor contents are written to the correct // offset. const arrayBuffer = new ArrayBuffer(testContents.byteLength + 4); const typedArray = new Uint32Array(arrayBuffer, 4); await mlContext.readTensor(mlTensor, typedArray); assert_array_equals(typedArray, testContents); }, `readTensor() with a TypedArray`); promise_test(async () => { const arrayBuffer = new ArrayBuffer(testContents.byteLength * 2); await mlContext.readTensor(mlTensor, arrayBuffer); assert_array_equals( new Uint32Array(arrayBuffer).subarray(0, testContents.length), testContents); // The rest of the array should remain uninitialized. assert_array_equals( new Uint32Array(arrayBuffer) .subarray(testContents.length, testContents.length * 2), new Uint32Array(testContents.length)); }, `readTensor() with a larger ArrayBuffer`); promise_test(async () => { // Create a slightly larger ArrayBuffer and set up the TypedArray at an // offset to make sure the MLTensor contents are written to the correct // offset. const arrayBuffer = new ArrayBuffer(testContents.byteLength * 2 + 4); const typedArray = new Uint32Array(arrayBuffer, 4); await mlContext.readTensor(mlTensor, typedArray); assert_array_equals( typedArray.subarray(0, testContents.length), testContents); // The rest of the array should remain uninitialized. assert_array_equals( typedArray.subarray(testContents.length, testContents.length * 2), new Uint32Array(testContents.length)); }, `readTensor() with a larger TypedArray`); promise_test(async (t) => { const tensor = await mlContext.createTensor({ dataType: 'int32', shape: [2, 2], readable: true, }); const arrayBufferView = new Int32Array(2 * 2); const arrayBuffer = arrayBufferView.buffer; // Reading a destroyed MLTensor should reject. tensor.destroy(); await promise_rejects_dom( t, 'InvalidStateError', mlContext.readTensor(tensor, arrayBuffer)); await promise_rejects_dom( t, 'InvalidStateError', mlContext.readTensor(tensor, arrayBufferView)); }, `readTensor() rejects on a destroyed MLTensor`); promise_test(async (t) => { const tensor = await mlContext.createTensor({ dataType: 'int32', shape: [2, 2], readable: true, }); const arrayBufferView = new Int32Array(2 * 2); const arrayBuffer = arrayBufferView.buffer; const checks = Promise.all([ promise_rejects_dom( t, 'InvalidStateError', mlContext.readTensor(tensor, arrayBuffer)), promise_rejects_dom( t, 'InvalidStateError', mlContext.readTensor(tensor, arrayBufferView)), ]); tensor.destroy(); await checks; }, `readTensor() rejects when the MLTensor is destroyed`);