From 9319e91d10dde5e59c092f03489a03ecc536bf82 Mon Sep 17 00:00:00 2001 From: shindli Date: Thu, 26 Jul 2018 14:23:43 +0300 Subject: [PATCH] Backed out 2 changesets (bug 1416723) for failures in dom/serviceworkers/test/test_serviceworker_interfaces.html on a CLOSED TREE Backed out changeset b2242216d11b (bug 1416723) Backed out changeset bfaf82051dfd (bug 1416723) --- js/public/ProtoKey.h | 7 + js/src/builtin/SIMD.cpp | 1644 +++++++++++++++ js/src/builtin/SIMD.h | 298 +++ js/src/builtin/SIMDConstants.h | 941 +++++++++ js/src/builtin/TestingFunctions.cpp | 7 +- js/src/builtin/TypedObject.cpp | 84 +- js/src/builtin/TypedObject.h | 52 +- js/src/builtin/TypedObject.js | 480 +++++ js/src/builtin/TypedObjectConstants.h | 23 +- .../automation/cgc-jittest-timeouts.txt | 1 + js/src/doc/JITOptimizations/Outcomes.md | 14 + js/src/jit-test/jit_test.py | 3 + js/src/jit-test/lib/simd.js | 109 + js/src/jit-test/tests/SIMD/anyall.js | 38 + js/src/jit-test/tests/SIMD/binary-arith.js | 30 + js/src/jit-test/tests/SIMD/bool32x4-arith.js | 15 + js/src/jit-test/tests/SIMD/bool32x4-const.js | 65 + js/src/jit-test/tests/SIMD/bug1109911.js | 11 + js/src/jit-test/tests/SIMD/bug1121299.js | 31 + js/src/jit-test/tests/SIMD/bug1123631.js | 9 + js/src/jit-test/tests/SIMD/bug1130845.js | 15 + js/src/jit-test/tests/SIMD/bug1241872.js | 10 + js/src/jit-test/tests/SIMD/bug1248503.js | 16 + js/src/jit-test/tests/SIMD/bug1273483.js | 9 + .../jit-test/tests/SIMD/bug1296640-gc-args.js | 9 + .../jit-test/tests/SIMD/bug1303780-gc-args.js | 12 + js/src/jit-test/tests/SIMD/bug1435317.js | 25 + js/src/jit-test/tests/SIMD/bug953108.js | 10 + js/src/jit-test/tests/SIMD/check.js | 25 + js/src/jit-test/tests/SIMD/compare.js | 39 + js/src/jit-test/tests/SIMD/complex-4.js | 70 + js/src/jit-test/tests/SIMD/convert.js | 68 + .../tests/SIMD/float32x4-binary-arith.js | 33 + js/src/jit-test/tests/SIMD/getters.js | 48 + .../tests/SIMD/inline-missing-arguments.js | 81 + js/src/jit-test/tests/SIMD/load.js | 123 ++ .../jit-test/tests/SIMD/nursery-overflow.js | 29 + js/src/jit-test/tests/SIMD/recover.js | 70 + js/src/jit-test/tests/SIMD/replacelane.js | 181 ++ js/src/jit-test/tests/SIMD/saturate.js | 37 + js/src/jit-test/tests/SIMD/select.js | 35 + js/src/jit-test/tests/SIMD/shift.js | 75 + js/src/jit-test/tests/SIMD/shuffle.js | 86 + js/src/jit-test/tests/SIMD/splat.js | 15 + js/src/jit-test/tests/SIMD/store.js | 143 ++ js/src/jit-test/tests/SIMD/swizzle.js | 104 + js/src/jit-test/tests/SIMD/uconvert.js | 86 + js/src/jit-test/tests/SIMD/unary.js | 35 + js/src/jit-test/tests/SIMD/unbox.js | 144 ++ js/src/jit-test/tests/asm.js/bug1126251.js | 38 + .../tests/asm.js/bug1201124-simd-proxy.js | 28 + js/src/jit-test/tests/asm.js/simd-fbirds.js | 198 ++ .../jit-test/tests/asm.js/simd-mandelbrot.js | 1819 ++++++++++++++++ .../tests/asm.js/testAsmJSWasmMixing.js | 11 + .../jit-test/tests/asm.js/testBug1099216.js | 61 + js/src/jit-test/tests/asm.js/testJumpRange.js | 20 + js/src/jit-test/tests/asm.js/testProfiling.js | 14 + js/src/jit-test/tests/asm.js/testSIMD-16x8.js | 525 +++++ js/src/jit-test/tests/asm.js/testSIMD-8x16.js | 539 +++++ .../tests/asm.js/testSIMD-bitcasts.js | 84 + .../tests/asm.js/testSIMD-load-store.js | 457 ++++ js/src/jit-test/tests/asm.js/testSIMD.js | 1575 ++++++++++++++ js/src/jit-test/tests/asm.js/testZOOB.js | 112 + js/src/jit/BaselineBailouts.cpp | 1 + js/src/jit/BaselineIC.cpp | 75 + js/src/jit/BaselineInspector.cpp | 19 + js/src/jit/BaselineInspector.h | 1 + js/src/jit/CodeGenerator.cpp | 110 +- js/src/jit/CodeGenerator.h | 15 + js/src/jit/EagerSimdUnbox.cpp | 128 ++ js/src/jit/EagerSimdUnbox.h | 25 + js/src/jit/InlinableNatives.h | 11 + js/src/jit/Ion.cpp | 28 + js/src/jit/IonBuilder.cpp | 18 + js/src/jit/IonBuilder.h | 45 + js/src/jit/IonOptimizationLevels.cpp | 2 + js/src/jit/IonOptimizationLevels.h | 8 + js/src/jit/IonTypes.h | 88 +- js/src/jit/JSJitFrameIter.h | 3 + js/src/jit/JitOptions.cpp | 3 + js/src/jit/JitOptions.h | 1 + js/src/jit/JitRealm.h | 33 +- js/src/jit/Lowering.cpp | 227 +- js/src/jit/MCallOptimize.cpp | 786 +++++++ js/src/jit/MIR.cpp | 525 +++++ js/src/jit/MIR.h | 1256 ++++++++++- js/src/jit/MIRGenerator.h | 6 + js/src/jit/MIRGraph.cpp | 33 + js/src/jit/MacroAssembler.cpp | 144 +- js/src/jit/MacroAssembler.h | 13 +- js/src/jit/RangeAnalysis.cpp | 4 + js/src/jit/Recover.cpp | 74 + js/src/jit/Recover.h | 12 + js/src/jit/TypePolicy.cpp | 124 ++ js/src/jit/TypePolicy.h | 61 + js/src/jit/TypedObjectPrediction.cpp | 7 + js/src/jit/TypedObjectPrediction.h | 3 +- js/src/jit/arm/CodeGenerator-arm.cpp | 234 +++ js/src/jit/arm/Lowering-arm.cpp | 60 + js/src/jit/arm/Lowering-arm.h | 11 + js/src/jit/arm/MacroAssembler-arm.h | 33 + js/src/jit/arm64/CodeGenerator-arm64.cpp | 234 +++ js/src/jit/arm64/Lowering-arm64.cpp | 60 + js/src/jit/arm64/Lowering-arm64.h | 12 + js/src/jit/arm64/MacroAssembler-arm64.cpp | 8 + js/src/jit/arm64/MacroAssembler-arm64.h | 38 + .../mips-shared/CodeGenerator-mips-shared.cpp | 234 +++ .../jit/mips-shared/Lowering-mips-shared.cpp | 60 + js/src/jit/mips-shared/Lowering-mips-shared.h | 11 + js/src/jit/mips32/MacroAssembler-mips32.h | 33 + js/src/jit/mips64/MacroAssembler-mips64.h | 33 + js/src/jit/none/Lowering-none.h | 8 + js/src/jit/none/MacroAssembler-none.h | 21 + js/src/jit/shared/Assembler-shared.h | 14 +- js/src/jit/shared/CodeGenerator-shared-inl.h | 4 + js/src/jit/shared/CodeGenerator-shared.cpp | 13 +- js/src/jit/shared/LIR-shared.h | 740 +++++++ js/src/jit/shared/Lowering-shared-inl.h | 3 + js/src/jit/x64/Assembler-x64.h | 2 +- js/src/jit/x64/CodeGenerator-x64.cpp | 8 + js/src/jit/x64/Lowering-x64.cpp | 8 + js/src/jit/x64/MacroAssembler-x64.cpp | 62 + .../x86-shared/CodeGenerator-x86-shared.cpp | 1793 +++++++++++++++- .../jit/x86-shared/CodeGenerator-x86-shared.h | 34 + js/src/jit/x86-shared/LIR-x86-shared.h | 43 + js/src/jit/x86-shared/Lowering-x86-shared.cpp | 377 ++++ js/src/jit/x86-shared/Lowering-x86-shared.h | 4 + .../MacroAssembler-x86-shared-SIMD.cpp | 1226 ----------- .../MacroAssembler-x86-shared-inl.h | 23 + .../x86-shared/MacroAssembler-x86-shared.h | 302 +-- js/src/jit/x86/Assembler-x86.h | 2 +- js/src/jit/x86/CodeGenerator-x86.cpp | 2 + js/src/jit/x86/Lowering-x86.cpp | 8 + js/src/jit/x86/MacroAssembler-x86.cpp | 62 + js/src/js.msg | 5 +- js/src/jsapi.cpp | 3 + js/src/jsfriendapi.h | 63 + js/src/moz.build | 4 +- js/src/tests/jstests.list | 17 + js/src/tests/non262/SIMD/ToSource.js | 56 + js/src/tests/non262/SIMD/binary-operations.js | 785 +++++++ js/src/tests/non262/SIMD/browser.js | 0 js/src/tests/non262/SIMD/bug1023145.js | 14 + js/src/tests/non262/SIMD/bug953270.js | 24 + js/src/tests/non262/SIMD/check.js | 214 ++ js/src/tests/non262/SIMD/comparisons.js | 349 ++++ js/src/tests/non262/SIMD/constructors.js | 226 ++ js/src/tests/non262/SIMD/conversions.js | 1261 +++++++++++ .../tests/non262/SIMD/float64x2-arithmetic.js | 69 + js/src/tests/non262/SIMD/load-floats.js | 19 + js/src/tests/non262/SIMD/load-int16x8.js | 17 + js/src/tests/non262/SIMD/load-int32x4.js | 18 + js/src/tests/non262/SIMD/load-int8x16.js | 17 + .../non262/SIMD/load-sab-buffer-compat.js | 49 + .../non262/SIMD/load-unsigned-integers.js | 22 + js/src/tests/non262/SIMD/minmax.js | 85 + js/src/tests/non262/SIMD/replaceLane.js | 226 ++ js/src/tests/non262/SIMD/select-bitselect.js | 133 ++ js/src/tests/non262/SIMD/shell.js | 580 ++++++ js/src/tests/non262/SIMD/shifts.js | 202 ++ js/src/tests/non262/SIMD/splat.js | 97 + js/src/tests/non262/SIMD/store.js | 264 +++ js/src/tests/non262/SIMD/swizzle-shuffle.js | 507 +++++ js/src/tests/non262/SIMD/toString.js | 90 + js/src/tests/non262/SIMD/typedobjects.js | 1077 ++++++++++ js/src/tests/non262/SIMD/unary-operations.js | 410 ++++ .../tests/non262/TypedObject/method_from.js | 3 + js/src/vm/ArrayBufferObject.cpp | 4 +- js/src/vm/GlobalObject.h | 18 + js/src/vm/JSContext.cpp | 6 - js/src/vm/SelfHosting.cpp | 15 + js/src/vm/TypedArrayObject.cpp | 8 + js/src/vm/TypedArrayObject.h | 5 + js/src/wasm/AsmJS.cpp | 1839 ++++++++++++++++- js/src/wasm/WasmBaselineCompile.cpp | 10 +- js/src/wasm/WasmBaselineCompile.h | 2 +- js/src/wasm/WasmBinaryConstants.h | 87 + js/src/wasm/WasmBuiltins.cpp | 2 + js/src/wasm/WasmInstance.cpp | 114 +- js/src/wasm/WasmIonCompile.cpp | 917 +++++++- js/src/wasm/WasmJS.cpp | 18 + js/src/wasm/WasmOpIter.cpp | 220 ++ js/src/wasm/WasmOpIter.h | 338 +++ js/src/wasm/WasmSignalHandlers.cpp | 10 +- js/src/wasm/WasmStubs.cpp | 81 +- js/src/wasm/WasmTypes.cpp | 30 + js/src/wasm/WasmTypes.h | 194 +- js/src/wasm/WasmValidate.cpp | 11 + js/src/wasm/WasmValidate.h | 44 + 189 files changed, 29973 insertions(+), 1591 deletions(-) create mode 100644 js/src/builtin/SIMD.cpp create mode 100644 js/src/builtin/SIMD.h create mode 100644 js/src/builtin/SIMDConstants.h create mode 100644 js/src/jit-test/lib/simd.js create mode 100644 js/src/jit-test/tests/SIMD/anyall.js create mode 100644 js/src/jit-test/tests/SIMD/binary-arith.js create mode 100644 js/src/jit-test/tests/SIMD/bool32x4-arith.js create mode 100644 js/src/jit-test/tests/SIMD/bool32x4-const.js create mode 100644 js/src/jit-test/tests/SIMD/bug1109911.js create mode 100644 js/src/jit-test/tests/SIMD/bug1121299.js create mode 100644 js/src/jit-test/tests/SIMD/bug1123631.js create mode 100644 js/src/jit-test/tests/SIMD/bug1130845.js create mode 100644 js/src/jit-test/tests/SIMD/bug1241872.js create mode 100644 js/src/jit-test/tests/SIMD/bug1248503.js create mode 100644 js/src/jit-test/tests/SIMD/bug1273483.js create mode 100644 js/src/jit-test/tests/SIMD/bug1296640-gc-args.js create mode 100644 js/src/jit-test/tests/SIMD/bug1303780-gc-args.js create mode 100644 js/src/jit-test/tests/SIMD/bug1435317.js create mode 100644 js/src/jit-test/tests/SIMD/bug953108.js create mode 100644 js/src/jit-test/tests/SIMD/check.js create mode 100644 js/src/jit-test/tests/SIMD/compare.js create mode 100644 js/src/jit-test/tests/SIMD/complex-4.js create mode 100644 js/src/jit-test/tests/SIMD/convert.js create mode 100644 js/src/jit-test/tests/SIMD/float32x4-binary-arith.js create mode 100644 js/src/jit-test/tests/SIMD/getters.js create mode 100644 js/src/jit-test/tests/SIMD/inline-missing-arguments.js create mode 100644 js/src/jit-test/tests/SIMD/load.js create mode 100644 js/src/jit-test/tests/SIMD/nursery-overflow.js create mode 100644 js/src/jit-test/tests/SIMD/recover.js create mode 100644 js/src/jit-test/tests/SIMD/replacelane.js create mode 100644 js/src/jit-test/tests/SIMD/saturate.js create mode 100644 js/src/jit-test/tests/SIMD/select.js create mode 100644 js/src/jit-test/tests/SIMD/shift.js create mode 100644 js/src/jit-test/tests/SIMD/shuffle.js create mode 100644 js/src/jit-test/tests/SIMD/splat.js create mode 100644 js/src/jit-test/tests/SIMD/store.js create mode 100644 js/src/jit-test/tests/SIMD/swizzle.js create mode 100644 js/src/jit-test/tests/SIMD/uconvert.js create mode 100644 js/src/jit-test/tests/SIMD/unary.js create mode 100644 js/src/jit-test/tests/SIMD/unbox.js create mode 100644 js/src/jit-test/tests/asm.js/bug1201124-simd-proxy.js create mode 100644 js/src/jit-test/tests/asm.js/simd-fbirds.js create mode 100644 js/src/jit-test/tests/asm.js/simd-mandelbrot.js create mode 100644 js/src/jit-test/tests/asm.js/testBug1099216.js create mode 100644 js/src/jit-test/tests/asm.js/testSIMD-16x8.js create mode 100644 js/src/jit-test/tests/asm.js/testSIMD-8x16.js create mode 100644 js/src/jit-test/tests/asm.js/testSIMD-bitcasts.js create mode 100644 js/src/jit-test/tests/asm.js/testSIMD-load-store.js create mode 100644 js/src/jit-test/tests/asm.js/testSIMD.js create mode 100644 js/src/jit/EagerSimdUnbox.cpp create mode 100644 js/src/jit/EagerSimdUnbox.h delete mode 100644 js/src/jit/x86-shared/MacroAssembler-x86-shared-SIMD.cpp create mode 100644 js/src/tests/non262/SIMD/ToSource.js create mode 100644 js/src/tests/non262/SIMD/binary-operations.js create mode 100644 js/src/tests/non262/SIMD/browser.js create mode 100644 js/src/tests/non262/SIMD/bug1023145.js create mode 100644 js/src/tests/non262/SIMD/bug953270.js create mode 100644 js/src/tests/non262/SIMD/check.js create mode 100644 js/src/tests/non262/SIMD/comparisons.js create mode 100644 js/src/tests/non262/SIMD/constructors.js create mode 100644 js/src/tests/non262/SIMD/conversions.js create mode 100644 js/src/tests/non262/SIMD/float64x2-arithmetic.js create mode 100644 js/src/tests/non262/SIMD/load-floats.js create mode 100644 js/src/tests/non262/SIMD/load-int16x8.js create mode 100644 js/src/tests/non262/SIMD/load-int32x4.js create mode 100644 js/src/tests/non262/SIMD/load-int8x16.js create mode 100644 js/src/tests/non262/SIMD/load-sab-buffer-compat.js create mode 100644 js/src/tests/non262/SIMD/load-unsigned-integers.js create mode 100644 js/src/tests/non262/SIMD/minmax.js create mode 100644 js/src/tests/non262/SIMD/replaceLane.js create mode 100644 js/src/tests/non262/SIMD/select-bitselect.js create mode 100644 js/src/tests/non262/SIMD/shell.js create mode 100644 js/src/tests/non262/SIMD/shifts.js create mode 100644 js/src/tests/non262/SIMD/splat.js create mode 100644 js/src/tests/non262/SIMD/store.js create mode 100644 js/src/tests/non262/SIMD/swizzle-shuffle.js create mode 100644 js/src/tests/non262/SIMD/toString.js create mode 100644 js/src/tests/non262/SIMD/typedobjects.js create mode 100644 js/src/tests/non262/SIMD/unary-operations.js diff --git a/js/public/ProtoKey.h b/js/public/ProtoKey.h index 3a48033b850c..f11acf7a9a94 100644 --- a/js/public/ProtoKey.h +++ b/js/public/ProtoKey.h @@ -48,6 +48,12 @@ #define IF_BDATA(real,imaginary) imaginary #endif +#ifdef ENABLE_SIMD +# define IF_SIMD(real,imaginary) real +#else +# define IF_SIMD(real,imaginary) imaginary +#endif + #ifdef ENABLE_SHARED_ARRAY_BUFFER #define IF_SAB(real,imaginary) real #else @@ -100,6 +106,7 @@ IF_SAB(real,imaginary)(SharedArrayBuffer, InitViaClassSpec, OCLASP(SharedA IF_INTL(real,imaginary) (Intl, InitIntlClass, CLASP(Intl)) \ IF_BDATA(real,imaginary)(TypedObject, InitTypedObjectModuleObject, OCLASP(TypedObjectModule)) \ real(Reflect, InitReflect, nullptr) \ +IF_SIMD(real,imaginary)(SIMD, InitSimdClass, OCLASP(Simd)) \ real(WeakSet, InitWeakSetClass, OCLASP(WeakSet)) \ real(TypedArray, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \ diff --git a/js/src/builtin/SIMD.cpp b/js/src/builtin/SIMD.cpp new file mode 100644 index 000000000000..7be8998f4f27 --- /dev/null +++ b/js/src/builtin/SIMD.cpp @@ -0,0 +1,1644 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * JS SIMD pseudo-module. + * Specification matches polyfill: + * https://github.com/johnmccutchan/ecmascript_simd/blob/master/src/ecmascript_simd.js + * The objects float32x4 and int32x4 are installed on the SIMD pseudo-module. + */ + +#include "builtin/SIMD.h" + +#include "mozilla/FloatingPoint.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/Sprintf.h" +#include "mozilla/TypeTraits.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jsnum.h" + +#include "builtin/TypedObject.h" +#include "jit/AtomicOperations.h" +#include "jit/InlinableNatives.h" +#include "js/Value.h" + +#include "vm/JSObject-inl.h" + +using namespace js; + +using mozilla::IsNaN; +using mozilla::EnableIf; +using mozilla::IsIntegral; +using mozilla::IsFloatingPoint; +using mozilla::IsSigned; +using mozilla::MakeUnsigned; + +/////////////////////////////////////////////////////////////////////////// +// SIMD + +static_assert(unsigned(SimdType::Count) == 12, "sync with TypedObjectConstants.h"); + +static bool ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane); + +static bool +CheckVectorObject(HandleValue v, SimdType expectedType) +{ + if (!v.isObject()) + return false; + + JSObject& obj = v.toObject(); + if (!obj.is()) + return false; + + TypeDescr& typeRepr = obj.as().typeDescr(); + if (typeRepr.kind() != type::Simd) + return false; + + return typeRepr.as().type() == expectedType; +} + +template +bool +js::IsVectorObject(HandleValue v) +{ + return CheckVectorObject(v, V::type); +} + +#define FOR_EACH_SIMD(macro) \ + macro(Int8x16) \ + macro(Int16x8) \ + macro(Int32x4) \ + macro(Uint8x16) \ + macro(Uint16x8) \ + macro(Uint32x4) \ + macro(Float32x4) \ + macro(Float64x2) \ + macro(Bool8x16) \ + macro(Bool16x8) \ + macro(Bool32x4) \ + macro(Bool64x2) + +#define InstantiateIsVectorObject_(T) \ + template bool js::IsVectorObject(HandleValue v); +FOR_EACH_SIMD(InstantiateIsVectorObject_) +#undef InstantiateIsVectorObject_ + +const char* +js::SimdTypeToString(SimdType type) +{ + switch (type) { +#define RETSTR_(TYPE) case SimdType::TYPE: return #TYPE; + FOR_EACH_SIMD(RETSTR_) +#undef RETSTR_ + case SimdType::Count: break; + } + return ""; +} + +PropertyName* +js::SimdTypeToName(const JSAtomState& atoms, SimdType type) +{ + switch (type) { +#define CASE_(TypeName) case SimdType::TypeName: return atoms.TypeName; + FOR_EACH_SIMD(CASE_) +#undef CASE_ + case SimdType::Count: break; + } + MOZ_CRASH("bad SIMD type"); +} + +bool +js::IsSimdTypeName(const JSAtomState& atoms, const PropertyName* name, SimdType* type) +{ +#define CHECK_(TypeName) if (name == atoms.TypeName) { \ + *type = SimdType::TypeName; \ + return true; \ + } + FOR_EACH_SIMD(CHECK_) +#undef CHECK_ + return false; +} + +static inline bool +ErrorBadArgs(JSContext* cx) +{ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); + return false; +} + +static inline bool +ErrorWrongTypeArg(JSContext* cx, unsigned argIndex, Handle typeDescr) +{ + MOZ_ASSERT(argIndex < 10); + char charArgIndex[2]; + SprintfLiteral(charArgIndex, "%u", argIndex); + + HeapSlot& typeNameSlot = typeDescr->getReservedSlotRef(JS_DESCR_SLOT_STRING_REPR); + char* typeNameStr = JS_EncodeString(cx, typeNameSlot.toString()); + if (!typeNameStr) + return false; + + JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR, + typeNameStr, charArgIndex); + JS_free(cx, typeNameStr); + return false; +} + +static inline bool +ErrorBadIndex(JSContext* cx) +{ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; +} + +/* Non-standard: convert and range check an index value for SIMD operations. + * + * 1. numericIndex = ToNumber(argument) (may throw TypeError) + * 2. intIndex = ToInteger(numericIndex) + * 3. if intIndex != numericIndex throw RangeError + * + * This function additionally bounds the range to the non-negative contiguous + * integers: + * + * 4. if intIndex < 0 or intIndex > 2^53 throw RangeError + * + * Return true and set |*index| to the integer value if |argument| is a valid + * array index argument. Otherwise report an TypeError or RangeError and return + * false. + * + * The returned index will always be in the range 0 <= *index <= 2^53. + */ +static bool +NonStandardToIndex(JSContext* cx, HandleValue v, uint64_t* index) +{ + // Fast common case. + if (v.isInt32()) { + int32_t i = v.toInt32(); + if (i >= 0) { + *index = i; + return true; + } + } + + // Slow case. Use ToNumber() to coerce. This may throw a TypeError. + double d; + if (!ToNumber(cx, v, &d)) + return false; + + // Check that |d| is an integer in the valid range. + // + // Not all floating point integers fit in the range of a uint64_t, so we + // need a rough range check before the real range check in our caller. We + // could limit indexes to UINT64_MAX, but this would mean that our callers + // have to be very careful about integer overflow. The contiguous integer + // floating point numbers end at 2^53, so make that our upper limit. If we + // ever support arrays with more than 2^53 elements, this will need to + // change. + // + // Reject infinities, NaNs, and numbers outside the contiguous integer range + // with a RangeError. + + // Write relation so NaNs throw a RangeError. + if (!(0 <= d && d <= (uint64_t(1) << 53))) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + // Check that d is an integer, throw a RangeError if not. + // Note that this conversion could invoke undefined behaviour without the + // range check above. + uint64_t i(d); + if (d != double(i)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + *index = i; + return true; +} + +template +static SimdTypeDescr* +GetTypeDescr(JSContext* cx) +{ + RootedGlobalObject global(cx, cx->global()); + return GlobalObject::getOrCreateSimdTypeDescr(cx, global, T::type); +} + +template +bool +js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out) +{ + typedef typename V::Elem Elem; + Rooted typeDescr(cx, GetTypeDescr(cx)); + if (!typeDescr) + return false; + if (!IsVectorObject(v)) + return ErrorWrongTypeArg(cx, 1, typeDescr); + + JS::AutoCheckCannotGC nogc(cx); + Elem* mem = reinterpret_cast(v.toObject().as().typedMem(nogc)); + *out = jit::SimdConstant::CreateSimd128(mem); + return true; +} + +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); +template bool js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); + +template +static Elem +TypedObjectMemory(HandleValue v, const JS::AutoRequireNoGC& nogc) +{ + TypedObject& obj = v.toObject().as(); + return reinterpret_cast(obj.typedMem(nogc)); +} + +static const ClassOps SimdTypeDescrClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + TypeDescr::finalize, + SimdTypeDescr::call +}; + +const Class SimdTypeDescr::class_ = { + "SIMD", + JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE, + &SimdTypeDescrClassOps +}; + +namespace { + +// Define classes (Int8x16Defn, Int16x8Defn, etc.) to group together various +// properties and so on. +#define DEFINE_DEFN_(TypeName) \ +class TypeName##Defn { \ + public: \ + static const JSFunctionSpec Methods[]; \ +}; + +FOR_EACH_SIMD(DEFINE_DEFN_) +#undef DEFINE_DEFN_ + +} // namespace + +// Shared type descriptor methods for all SIMD types. +static const JSFunctionSpec TypeDescriptorMethods[] = { + JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0), + JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0), + JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0), + JS_FS_END +}; + +// Shared TypedObject methods for all SIMD types. +static const JSFunctionSpec SimdTypedObjectMethods[] = { + JS_SELF_HOSTED_FN("toString", "SimdToString", 0, 0), + JS_SELF_HOSTED_FN("valueOf", "SimdValueOf", 0, 0), + JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0), + JS_FS_END +}; + +// Provide JSJitInfo structs for those types that are supported by Ion. +// The controlling SIMD type is encoded as the InlinableNative primary opcode. +// The SimdOperation within the type is encoded in the .depth field. +// +// The JS_INLINABLE_FN macro refers to js::JitInfo_##native which we provide as +// Simd##Type##_##Operation +// +// /!\ Don't forget to keep this list in sync with the SIMD instrinics used in +// SelfHosting.cpp. + +namespace js { +namespace jit { + +static_assert(uint64_t(SimdOperation::Last) <= UINT16_MAX, "SimdOperation must fit in uint16_t"); + +// See also JitInfo_* in MCallOptimize.cpp. We provide a JSJitInfo for all the +// named functions here. The default JitInfo_SimdInt32x4 etc structs represent the +// SimdOperation::Constructor. +#define DEFN(TYPE, OP) const JSJitInfo JitInfo_Simd##TYPE##_##OP = { \ + /* .getter, unused for inlinable natives. */ \ + { nullptr }, \ + /* .inlinableNative, but we have to init first union member: .protoID. */ \ + { uint16_t(InlinableNative::Simd##TYPE) }, \ + /* .nativeOp. Actually initializing first union member .depth. */ \ + { uint16_t(SimdOperation::Fn_##OP) }, \ + /* .type_ bitfield says this in an inlinable native function. */ \ + JSJitInfo::InlinableNative \ + /* Remaining fields are not used for inlinable natives. They are zero-initialized. */ \ +}; + +// This list of inlinable types should match the one in jit/InlinableNatives.h. +#define TDEFN(Name, Func, Operands) DEFN(Float32x4, Name) +FLOAT32X4_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Int8x16, Name) +INT8X16_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Uint8x16, Name) +UINT8X16_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Int16x8, Name) +INT16X8_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Uint16x8, Name) +UINT16X8_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Int32x4, Name) +INT32X4_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Uint32x4, Name) +UINT32X4_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Bool8x16, Name) +BOOL8X16_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Bool16x8, Name) +BOOL16X8_FUNCTION_LIST(TDEFN) +#undef TDEFN + +#define TDEFN(Name, Func, Operands) DEFN(Bool32x4, Name) +BOOL32X4_FUNCTION_LIST(TDEFN) +#undef TDEFN + +} // namespace jit +} // namespace js + +const JSFunctionSpec Float32x4Defn::Methods[] = { +#define SIMD_FLOAT32X4_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_float32x4_##Name, Operands, 0, SimdFloat32x4_##Name), + FLOAT32X4_FUNCTION_LIST(SIMD_FLOAT32X4_FUNCTION_ITEM) +#undef SIMD_FLOAT32x4_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Float64x2Defn::Methods[] = { +#define SIMD_FLOAT64X2_FUNCTION_ITEM(Name, Func, Operands) \ + JS_FN(#Name, js::simd_float64x2_##Name, Operands, 0), + FLOAT64X2_FUNCTION_LIST(SIMD_FLOAT64X2_FUNCTION_ITEM) +#undef SIMD_FLOAT64X2_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Int8x16Defn::Methods[] = { +#define SIMD_INT8X16_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_int8x16_##Name, Operands, 0, SimdInt8x16_##Name), + INT8X16_FUNCTION_LIST(SIMD_INT8X16_FUNCTION_ITEM) +#undef SIMD_INT8X16_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Int16x8Defn::Methods[] = { +#define SIMD_INT16X8_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_int16x8_##Name, Operands, 0, SimdInt16x8_##Name), + INT16X8_FUNCTION_LIST(SIMD_INT16X8_FUNCTION_ITEM) +#undef SIMD_INT16X8_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Int32x4Defn::Methods[] = { +#define SIMD_INT32X4_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_int32x4_##Name, Operands, 0, SimdInt32x4_##Name), + INT32X4_FUNCTION_LIST(SIMD_INT32X4_FUNCTION_ITEM) +#undef SIMD_INT32X4_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Uint8x16Defn::Methods[] = { +#define SIMD_UINT8X16_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_uint8x16_##Name, Operands, 0, SimdUint8x16_##Name), + UINT8X16_FUNCTION_LIST(SIMD_UINT8X16_FUNCTION_ITEM) +#undef SIMD_UINT8X16_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Uint16x8Defn::Methods[] = { +#define SIMD_UINT16X8_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_uint16x8_##Name, Operands, 0, SimdUint16x8_##Name), + UINT16X8_FUNCTION_LIST(SIMD_UINT16X8_FUNCTION_ITEM) +#undef SIMD_UINT16X8_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Uint32x4Defn::Methods[] = { +#define SIMD_UINT32X4_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_uint32x4_##Name, Operands, 0, SimdUint32x4_##Name), + UINT32X4_FUNCTION_LIST(SIMD_UINT32X4_FUNCTION_ITEM) +#undef SIMD_UINT32X4_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Bool8x16Defn::Methods[] = { +#define SIMD_BOOL8X16_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_bool8x16_##Name, Operands, 0, SimdBool8x16_##Name), + BOOL8X16_FUNCTION_LIST(SIMD_BOOL8X16_FUNCTION_ITEM) +#undef SIMD_BOOL8X16_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Bool16x8Defn::Methods[] = { +#define SIMD_BOOL16X8_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_bool16x8_##Name, Operands, 0, SimdBool16x8_##Name), + BOOL16X8_FUNCTION_LIST(SIMD_BOOL16X8_FUNCTION_ITEM) +#undef SIMD_BOOL16X8_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Bool32x4Defn::Methods[] = { +#define SIMD_BOOL32X4_FUNCTION_ITEM(Name, Func, Operands) \ + JS_INLINABLE_FN(#Name, js::simd_bool32x4_##Name, Operands, 0, SimdBool32x4_##Name), + BOOL32X4_FUNCTION_LIST(SIMD_BOOL32X4_FUNCTION_ITEM) +#undef SIMD_BOOL32X4_FUNCTION_ITEM + JS_FS_END +}; + +const JSFunctionSpec Bool64x2Defn::Methods[] = { +#define SIMD_BOOL64X2_FUNCTION_ITEM(Name, Func, Operands) \ + JS_FN(#Name, js::simd_bool64x2_##Name, Operands, 0), + BOOL64X2_FUNCTION_LIST(SIMD_BOOL64X2_FUNCTION_ITEM) +#undef SIMD_BOOL64x2_FUNCTION_ITEM + JS_FS_END +}; + +template +static bool +FillLanes(JSContext* cx, Handle result, const CallArgs& args) +{ + typedef typename T::Elem Elem; + Elem tmp; + for (unsigned i = 0; i < T::lanes; i++) { + if (!T::Cast(cx, args.get(i), &tmp)) + return false; + // Reassure typedMem() that we won't GC while holding onto the returned + // pointer, even though we could GC on every iteration of this loop + // (but it is safe because we re-fetch each time.) + JS::AutoCheckCannotGC nogc(cx); + reinterpret_cast(result->typedMem(nogc))[i] = tmp; + } + args.rval().setObject(*result); + return true; +} + +bool +SimdTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted descr(cx, &args.callee().as()); + Rooted result(cx, TypedObject::createZeroed(cx, descr)); + if (!result) + return false; + +#define CASE_CALL_(Type) \ + case SimdType::Type: return FillLanes< ::Type>(cx, result, args); + + switch (descr->type()) { + FOR_EACH_SIMD(CASE_CALL_) + case SimdType::Count: break; + } + +#undef CASE_CALL_ + MOZ_CRASH("unexpected SIMD descriptor"); + return false; +} + +/////////////////////////////////////////////////////////////////////////// +// SIMD class + +static const ClassOps SimdObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* enumerate */ + nullptr, /* newEnumerate */ + SimdObject::resolve +}; + +const Class SimdObject::class_ = { + "SIMD", + JSCLASS_HAS_RESERVED_SLOTS(uint32_t(SimdType::Count)), + &SimdObjectClassOps +}; + +/* static */ bool +GlobalObject::initSimdObject(JSContext* cx, Handle global) +{ + // SIMD relies on the TypedObject module being initialized. + // In particular, the self-hosted code for array() wants + // to be able to call GetTypedObjectModule(). It is NOT necessary + // to install the TypedObjectModule global, but at the moment + // those two things are not separable. + if (!GlobalObject::getOrCreateTypedObjectModule(cx, global)) + return false; + + RootedObject globalSimdObject(cx); + RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); + if (!objProto) + return false; + + globalSimdObject = NewObjectWithGivenProto(cx, &SimdObject::class_, objProto, SingletonObject); + if (!globalSimdObject) + return false; + + RootedValue globalSimdValue(cx, ObjectValue(*globalSimdObject)); + if (!DefineDataProperty(cx, global, cx->names().SIMD, globalSimdValue, JSPROP_RESOLVING)) + return false; + + global->setConstructor(JSProto_SIMD, globalSimdValue); + return true; +} + +static bool +CreateSimdType(JSContext* cx, Handle global, HandlePropertyName stringRepr, + SimdType simdType, const JSFunctionSpec* methods) +{ + RootedObject funcProto(cx, GlobalObject::getOrCreateFunctionPrototype(cx, global)); + if (!funcProto) + return false; + + // Create type constructor itself and initialize its reserved slots. + Rooted typeDescr(cx); + typeDescr = NewObjectWithGivenProto(cx, funcProto, SingletonObject); + if (!typeDescr) + return false; + + typeDescr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Simd)); + typeDescr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr)); + typeDescr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(SimdTypeDescr::alignment(simdType))); + typeDescr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(SimdTypeDescr::size(simdType))); + typeDescr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false)); + typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(uint8_t(simdType))); + + if (!CreateUserSizeAndAlignmentProperties(cx, typeDescr)) + return false; + + // Create prototype property, which inherits from Object.prototype. + RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global)); + if (!objProto) + return false; + Rooted proto(cx); + proto = NewObjectWithGivenProto(cx, objProto, SingletonObject); + if (!proto) + return false; + typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto)); + + // Link constructor to prototype and install properties. + if (!JS_DefineFunctions(cx, typeDescr, TypeDescriptorMethods)) + return false; + + if (!LinkConstructorAndPrototype(cx, typeDescr, proto) || + !JS_DefineFunctions(cx, proto, SimdTypedObjectMethods)) + { + return false; + } + + // Bind type descriptor to the global SIMD object + RootedObject globalSimdObject(cx, GlobalObject::getOrCreateSimdGlobalObject(cx, global)); + MOZ_ASSERT(globalSimdObject); + + RootedValue typeValue(cx, ObjectValue(*typeDescr)); + if (!JS_DefineFunctions(cx, typeDescr, methods) || + !DefineDataProperty(cx, globalSimdObject, stringRepr, typeValue, + JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) + { + return false; + } + + uint32_t slot = uint32_t(typeDescr->type()); + MOZ_ASSERT(globalSimdObject->as().getReservedSlot(slot).isUndefined()); + globalSimdObject->as().setReservedSlot(slot, ObjectValue(*typeDescr)); + return !!typeDescr; +} + +/* static */ bool +GlobalObject::initSimdType(JSContext* cx, Handle global, SimdType simdType) +{ +#define CREATE_(Type) \ + case SimdType::Type: \ + return CreateSimdType(cx, global, cx->names().Type, simdType, Type##Defn::Methods); + + switch (simdType) { + FOR_EACH_SIMD(CREATE_) + case SimdType::Count: break; + } + MOZ_CRASH("unexpected simd type"); + +#undef CREATE_ +} + +/* static */ SimdTypeDescr* +GlobalObject::getOrCreateSimdTypeDescr(JSContext* cx, Handle global, + SimdType simdType) +{ + MOZ_ASSERT(unsigned(simdType) < unsigned(SimdType::Count), "Invalid SIMD type"); + + RootedObject globalSimdObject(cx, GlobalObject::getOrCreateSimdGlobalObject(cx, global)); + if (!globalSimdObject) + return nullptr; + + uint32_t typeSlotIndex = uint32_t(simdType); + if (globalSimdObject->as().getReservedSlot(typeSlotIndex).isUndefined() && + !GlobalObject::initSimdType(cx, global, simdType)) + { + return nullptr; + } + + const Value& slot = globalSimdObject->as().getReservedSlot(typeSlotIndex); + MOZ_ASSERT(slot.isObject()); + return &slot.toObject().as(); +} + +bool +SimdObject::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved) +{ + *resolved = false; + if (!JSID_IS_ATOM(id)) + return true; + JSAtom* str = JSID_TO_ATOM(id); + Rooted global(cx, cx->global()); +#define TRY_RESOLVE_(Type) \ + if (str == cx->names().Type) { \ + *resolved = CreateSimdType(cx, global, cx->names().Type, \ + SimdType::Type, Type##Defn::Methods); \ + return *resolved; \ + } + FOR_EACH_SIMD(TRY_RESOLVE_) +#undef TRY_RESOLVE_ + return true; +} + +JSObject* +js::InitSimdClass(JSContext* cx, Handle global) +{ + return GlobalObject::getOrCreateSimdGlobalObject(cx, global); +} + +template +JSObject* +js::CreateSimd(JSContext* cx, const typename V::Elem* data) +{ + typedef typename V::Elem Elem; + Rooted typeDescr(cx, GetTypeDescr(cx)); + if (!typeDescr) + return nullptr; + + Rooted result(cx, TypedObject::createZeroed(cx, typeDescr)); + if (!result) + return nullptr; + + JS::AutoCheckCannotGC nogc(cx); + Elem* resultMem = reinterpret_cast(result->typedMem(nogc)); + memcpy(resultMem, data, sizeof(Elem) * V::lanes); + return result; +} + +#define InstantiateCreateSimd_(Type) \ + template JSObject* js::CreateSimd(JSContext* cx, const Type::Elem* data); + +FOR_EACH_SIMD(InstantiateCreateSimd_) + +#undef InstantiateCreateSimd_ + +#undef FOR_EACH_SIMD + +namespace js { + +namespace detail { + +template +struct MaybeMakeUnsigned { + using Type = T; +}; + +template +struct MaybeMakeUnsigned::value && IsSigned::value>::Type> { + using Type = typename MakeUnsigned::Type; +}; + +} // namespace detail + +// Unary SIMD operators +template +struct Identity { + static T apply(T x) { return x; } +}; +template +struct Abs { + static T apply(T x) { return mozilla::Abs(x); } +}; +template +struct Neg { + using MaybeUnsignedT = typename detail::MaybeMakeUnsigned::Type; + static T apply(T x) { + // Prepend |1U| to force integral promotion through *unsigned* types. + // Otherwise when |T = uint16_t| and |int| is 32-bit, we could have + // |uint16_t(-1) * uint16_t(65535)| which would really be + // |int(65535) * int(65535)|, but as |4294836225 > 2147483647| would + // perform signed integer overflow. + // https://stackoverflow.com/questions/24795651/whats-the-best-c-way-to-multiply-unsigned-integers-modularly-safely + return static_cast(1U * MaybeUnsignedT(-1) * MaybeUnsignedT(x)); + } +}; +template +struct Not { + static T apply(T x) { return ~x; } +}; +template +struct LogicalNot { + static T apply(T x) { return !x; } +}; +template +struct RecApprox { + static_assert(IsFloatingPoint::value, "RecApprox only supported for floating points"); + static T apply(T x) { return 1 / x; } +}; +template +struct RecSqrtApprox { + static_assert(IsFloatingPoint::value, "RecSqrtApprox only supported for floating points"); + static T apply(T x) { return 1 / sqrt(x); } +}; +template +struct Sqrt { + static_assert(IsFloatingPoint::value, "Sqrt only supported for floating points"); + static T apply(T x) { return sqrt(x); } +}; + +// Binary SIMD operators +template +struct Add { + using MaybeUnsignedT = typename detail::MaybeMakeUnsigned::Type; + static T apply(T l, T r) { return MaybeUnsignedT(l) + MaybeUnsignedT(r); } +}; +template +struct Sub { + using MaybeUnsignedT = typename detail::MaybeMakeUnsigned::Type; + static T apply(T l, T r) { return MaybeUnsignedT(l) - MaybeUnsignedT(r); } +}; +template +struct Div { + static_assert(IsFloatingPoint::value, "Div only supported for floating points"); + static T apply(T l, T r) { return l / r; } +}; +template +struct Mul { + using MaybeUnsignedT = typename detail::MaybeMakeUnsigned::Type; + static T apply(T l, T r) { return MaybeUnsignedT(l) * MaybeUnsignedT(r); } +}; +template +struct Minimum { + static T apply(T l, T r) { return math_min_impl(l, r); } +}; +template +struct MinNum { + static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_min_impl(l, r)); } +}; +template +struct Maximum { + static T apply(T l, T r) { return math_max_impl(l, r); } +}; +template +struct MaxNum { + static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_max_impl(l, r)); } +}; +template +struct LessThan { + static bool apply(T l, T r) { return l < r; } +}; +template +struct LessThanOrEqual { + static bool apply(T l, T r) { return l <= r; } +}; +template +struct GreaterThan { + static bool apply(T l, T r) { return l > r; } +}; +template +struct GreaterThanOrEqual { + static bool apply(T l, T r) { return l >= r; } +}; +template +struct Equal { + static bool apply(T l, T r) { return l == r; } +}; +template +struct NotEqual { + static bool apply(T l, T r) { return l != r; } +}; +template +struct Xor { + static T apply(T l, T r) { return l ^ r; } +}; +template +struct And { + static T apply(T l, T r) { return l & r; } +}; +template +struct Or { + static T apply(T l, T r) { return l | r; } +}; + +// For the following three operators, if the value v we're trying to shift is +// such that v << bits can't fit in the int32 range, then we have undefined +// behavior, according to C++11 [expr.shift]p2. However, left-shifting an +// unsigned type is well-defined. +// +// In C++, shifting by an amount outside the range [0;N-1] is undefined +// behavior. SIMD.js reduces the shift amount modulo the number of bits in a +// lane and has defined behavior for all shift amounts. +template +struct ShiftLeft { + static T apply(T v, int32_t bits) { + typedef typename mozilla::MakeUnsigned::Type UnsignedT; + uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8); + return UnsignedT(v) << maskedBits; + } +}; +template +struct ShiftRightArithmetic { + static T apply(T v, int32_t bits) { + typedef typename mozilla::MakeSigned::Type SignedT; + uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8); + return SignedT(v) >> maskedBits; + } +}; +template +struct ShiftRightLogical { + static T apply(T v, int32_t bits) { + typedef typename mozilla::MakeUnsigned::Type UnsignedT; + uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8); + return UnsignedT(v) >> maskedBits; + } +}; + +// Saturating arithmetic is only defined on types smaller than int. +// Clamp `x` into the range supported by the integral type T. +template +static T +Saturate(int x) +{ + static_assert(mozilla::IsIntegral::value, "Only integer saturation supported"); + static_assert(sizeof(T) < sizeof(int), "Saturating int-sized arithmetic is not safe"); + const T lower = mozilla::MinValue::value; + const T upper = mozilla::MaxValue::value; + if (x > int(upper)) + return upper; + if (x < int(lower)) + return lower; + return T(x); +} + +// Since signed integer overflow is undefined behavior in C++, it would be +// wildly irresponsible to attempt something as dangerous as adding two numbers +// coming from user code. However, in this case we know that T is smaller than +// int, so there is no way these operations can cause overflow. The +// static_assert in Saturate() enforces this for us. +template +struct AddSaturate { + static T apply(T l, T r) { return Saturate(l + r); } +}; +template +struct SubSaturate { + static T apply(T l, T r) { return Saturate(l - r); } +}; + +} // namespace js + +template +static bool +StoreResult(JSContext* cx, CallArgs& args, typename Out::Elem* result) +{ + RootedObject obj(cx, CreateSimd(cx, result)); + if (!obj) + return false; + args.rval().setObject(*obj); + return true; +} + +// StoreResult can GC, and it is commonly used after pulling something out of a +// TypedObject: +// +// Elem result = op(TypedObjectMemory(args[0])); +// StoreResult(..., result); +// +// The pointer extracted from the typed object in args[0] in the above example +// could be an interior pointer, and therefore be invalidated by GC. +// TypedObjectMemory() requires an assertion token to be passed in to prove +// that we won't GC, but the scope of eg an AutoCheckCannotGC RAII object +// extends to the end of its containing scope -- which would include the call +// to StoreResult, resulting in a rooting hazard. +// +// TypedObjectElemArray fixes this by wrapping the problematic pointer in a +// type, and the analysis is able to see that it is dead before calling +// StoreResult. (But if another GC called is made before the pointer is dead, +// it will correctly report a hazard.) +// +template +class TypedObjectElemArray { + Elem* elements; + public: + explicit TypedObjectElemArray(HandleValue objVal) { + JS::AutoCheckCannotGC nogc; + elements = TypedObjectMemory(objVal, nogc); + } + Elem& operator[](int i) { return elements[i]; } +} JS_HAZ_GC_POINTER; + +// Coerces the inputs of type In to the type Coercion, apply the operator Op +// and converts the result to the type Out. +template class Op, typename Out> +static bool +CoercedUnaryFunc(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename Coercion::Elem CoercionElem; + typedef typename Out::Elem RetElem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + CoercionElem result[Coercion::lanes]; + TypedObjectElemArray val(args[0]); + for (unsigned i = 0; i < Coercion::lanes; i++) + result[i] = Op::apply(val[i]); + return StoreResult(cx, args, (RetElem*) result); +} + +// Coerces the inputs of type In to the type Coercion, apply the operator Op +// and converts the result to the type Out. +template class Op, typename Out> +static bool +CoercedBinaryFunc(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename Coercion::Elem CoercionElem; + typedef typename Out::Elem RetElem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2 || !IsVectorObject(args[0]) || !IsVectorObject(args[1])) + return ErrorBadArgs(cx); + + CoercionElem result[Coercion::lanes]; + TypedObjectElemArray left(args[0]); + TypedObjectElemArray right(args[1]); + for (unsigned i = 0; i < Coercion::lanes; i++) + result[i] = Op::apply(left[i], right[i]); + return StoreResult(cx, args, (RetElem*) result); +} + +// Same as above, with no coercion, i.e. Coercion == In. +template class Op, typename Out> +static bool +UnaryFunc(JSContext* cx, unsigned argc, Value* vp) +{ + return CoercedUnaryFunc(cx, argc, vp); +} + +template class Op, typename Out> +static bool +BinaryFunc(JSContext* cx, unsigned argc, Value* vp) +{ + return CoercedBinaryFunc(cx, argc, vp); +} + +template +static bool +ExtractLane(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + unsigned lane; + if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane)) + return false; + + JS::AutoCheckCannotGC nogc(cx); + Elem* vec = TypedObjectMemory(args[0], nogc); + Elem val = vec[lane]; + args.rval().set(V::ToValue(val)); + return true; +} + +template +static bool +AllTrue(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + JS::AutoCheckCannotGC nogc(cx); + Elem* vec = TypedObjectMemory(args[0], nogc); + bool allTrue = true; + for (unsigned i = 0; allTrue && i < V::lanes; i++) + allTrue = vec[i]; + + args.rval().setBoolean(allTrue); + return true; +} + +template +static bool +AnyTrue(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + JS::AutoCheckCannotGC nogc(cx); + Elem* vec = TypedObjectMemory(args[0], nogc); + bool anyTrue = false; + for (unsigned i = 0; !anyTrue && i < V::lanes; i++) + anyTrue = vec[i]; + + args.rval().setBoolean(anyTrue); + return true; +} + +template +static bool +ReplaceLane(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + // Only the first and second arguments are mandatory + if (args.length() < 2 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + unsigned lane; + if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane)) + return false; + + Elem value; + if (!V::Cast(cx, args.get(2), &value)) + return false; + + TypedObjectElemArray vec(args[0]); + Elem result[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) + result[i] = i == lane ? value : vec[i]; + + return StoreResult(cx, args, result); +} + +template +static bool +Swizzle(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != (V::lanes + 1) || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + unsigned lanes[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) { + if (!ArgumentToLaneIndex(cx, args[i + 1], V::lanes, &lanes[i])) + return false; + } + + TypedObjectElemArray val(args[0]); + Elem result[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) + result[i] = val[lanes[i]]; + + return StoreResult(cx, args, result); +} + +template +static bool +Shuffle(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != (V::lanes + 2) || !IsVectorObject(args[0]) || !IsVectorObject(args[1])) + return ErrorBadArgs(cx); + + unsigned lanes[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) { + if (!ArgumentToLaneIndex(cx, args[i + 2], 2 * V::lanes, &lanes[i])) + return false; + } + + Elem result[V::lanes]; + { + JS::AutoCheckCannotGC nogc(cx); + Elem* lhs = TypedObjectMemory(args[0], nogc); + Elem* rhs = TypedObjectMemory(args[1], nogc); + + for (unsigned i = 0; i < V::lanes; i++) { + Elem* selectedInput = lanes[i] < V::lanes ? lhs : rhs; + result[i] = selectedInput[lanes[i] % V::lanes]; + } + } + + return StoreResult(cx, args, result); +} + +template class Op> +static bool +BinaryScalar(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) + return ErrorBadArgs(cx); + + if (!IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + int32_t bits; + if (!ToInt32(cx, args[1], &bits)) + return false; + + TypedObjectElemArray val(args[0]); + Elem result[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) + result[i] = Op::apply(val[i], bits); + + return StoreResult(cx, args, result); +} + +template class Op, typename Out> +static bool +CompareFunc(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename In::Elem InElem; + typedef typename Out::Elem OutElem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2 || !IsVectorObject(args[0]) || !IsVectorObject(args[1])) + return ErrorBadArgs(cx); + + OutElem result[Out::lanes]; + TypedObjectElemArray left(args[0]); + TypedObjectElemArray right(args[1]); + for (unsigned i = 0; i < Out::lanes; i++) { + unsigned j = (i * In::lanes) / Out::lanes; + result[i] = Op::apply(left[j], right[j]) ? -1 : 0; + } + + return StoreResult(cx, args, result); +} + +// This struct defines whether we should throw during a conversion attempt, +// when trying to convert a value of type from From to the type To. This +// happens whenever a C++ conversion would have undefined behavior (and perhaps +// be platform-dependent). +template +struct ThrowOnConvert; + +struct NeverThrow +{ + static bool value(int32_t v) { + return false; + } +}; + +// While int32 to float conversions can be lossy, these conversions have +// defined behavior in C++, so we don't need to care about them here. In practice, +// this means round to nearest, tie with even (zero bit in significand). +template<> +struct ThrowOnConvert : public NeverThrow {}; + +template<> +struct ThrowOnConvert : public NeverThrow {}; + +// All int32 can be safely converted to doubles. +template<> +struct ThrowOnConvert : public NeverThrow {}; + +template<> +struct ThrowOnConvert : public NeverThrow {}; + +// All floats can be safely converted to doubles. +template<> +struct ThrowOnConvert : public NeverThrow {}; + +// Double to float conversion for inputs which aren't in the float range are +// undefined behavior in C++, but they're defined in IEEE754. +template<> +struct ThrowOnConvert : public NeverThrow {}; + +// Float to integer conversions have undefined behavior if the float value +// is out of the representable integer range (on x86, will yield the undefined +// value pattern, namely 0x80000000; on arm, will clamp the input value), so +// check this here. +template +struct ThrowIfNotInRange +{ + static_assert(mozilla::IsIntegral::value, "bad destination type"); + + static bool value(From v) { + // Truncate to integer value before the range check. + double d = trunc(double(v)); + // Arrange relations so NaN returns true (i.e., it throws a RangeError). + return !(d >= double(mozilla::MinValue::value) && + d <= double(mozilla::MaxValue::value)); + } +}; + +template<> +struct ThrowOnConvert : public ThrowIfNotInRange {}; + +template<> +struct ThrowOnConvert : public ThrowIfNotInRange {}; + +template<> +struct ThrowOnConvert : public ThrowIfNotInRange {}; + +template<> +struct ThrowOnConvert : public ThrowIfNotInRange {}; + +template +static bool +FuncConvert(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + typedef typename Vret::Elem RetElem; + + static_assert(!mozilla::IsSame::value, "Can't convert SIMD type to itself"); + static_assert(V::lanes == Vret::lanes, "Can only convert from same number of lanes"); + static_assert(!mozilla::IsIntegral::value || !mozilla::IsIntegral::value, + "Cannot convert between integer SIMD types"); + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + TypedObjectElemArray val(args[0]); + RetElem result[Vret::lanes]; + for (unsigned i = 0; i < V::lanes; i++) { + if (ThrowOnConvert::value(val[i])) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SIMD_FAILED_CONVERSION); + return false; + } + result[i] = ConvertScalar(val[i]); + } + + return StoreResult(cx, args, result); +} + +template +static bool +FuncConvertBits(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + typedef typename Vret::Elem RetElem; + + static_assert(!mozilla::IsSame::value, "Can't convert SIMD type to itself"); + static_assert(V::lanes * sizeof(Elem) == Vret::lanes * sizeof(RetElem), + "Can only bitcast from the same number of bits"); + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1 || !IsVectorObject(args[0])) + return ErrorBadArgs(cx); + + // While we could just pass the typedMem of args[0] as StoreResults' last + // argument, a GC could move the pointer to its memory in the meanwhile. + // For consistency with other SIMD functions, simply copy the input in a + // temporary array. + RetElem copy[Vret::lanes]; + { + JS::AutoCheckCannotGC nogc(cx); + memcpy(copy, TypedObjectMemory(args[0], nogc), Vret::lanes * sizeof(RetElem)); + } + return StoreResult(cx, args, copy); +} + +template +static bool +FuncSplat(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename Vret::Elem RetElem; + + CallArgs args = CallArgsFromVp(argc, vp); + RetElem arg; + if (!Vret::Cast(cx, args.get(0), &arg)) + return false; + + RetElem result[Vret::lanes]; + for (unsigned i = 0; i < Vret::lanes; i++) + result[i] = arg; + return StoreResult(cx, args, result); +} + +template +static bool +Bool(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + + Elem result[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) + result[i] = ToBoolean(args.get(i)) ? -1 : 0; + return StoreResult(cx, args, result); +} + +template +static bool +SelectBits(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + typedef typename MaskType::Elem MaskTypeElem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 3 || !IsVectorObject(args[0]) || + !IsVectorObject(args[1]) || !IsVectorObject(args[2])) + { + return ErrorBadArgs(cx); + } + + TypedObjectElemArray val(args[0]); + TypedObjectElemArray tv(args[1]); + TypedObjectElemArray fv(args[2]); + + MaskTypeElem tr[MaskType::lanes]; + for (unsigned i = 0; i < MaskType::lanes; i++) + tr[i] = And::apply(val[i], tv[i]); + + MaskTypeElem fr[MaskType::lanes]; + for (unsigned i = 0; i < MaskType::lanes; i++) + fr[i] = And::apply(Not::apply(val[i]), fv[i]); + + MaskTypeElem orInt[MaskType::lanes]; + for (unsigned i = 0; i < MaskType::lanes; i++) + orInt[i] = Or::apply(tr[i], fr[i]); + + Elem* result = reinterpret_cast(orInt); + return StoreResult(cx, args, result); +} + +template +static bool +Select(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + typedef typename MaskType::Elem MaskTypeElem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 3 || !IsVectorObject(args[0]) || + !IsVectorObject(args[1]) || !IsVectorObject(args[2])) + { + return ErrorBadArgs(cx); + } + + TypedObjectElemArray mask(args[0]); + TypedObjectElemArray tv(args[1]); + TypedObjectElemArray fv(args[2]); + + Elem result[V::lanes]; + for (unsigned i = 0; i < V::lanes; i++) + result[i] = mask[i] ? tv[i] : fv[i]; + + return StoreResult(cx, args, result); +} + +// Extract an integer lane index from a function argument. +// +// Register an exception and return false if the argument is not suitable. +static bool +ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane) +{ + uint64_t arg; + if (!NonStandardToIndex(cx, v, &arg)) + return false; + if (arg >= limit) + return ErrorBadIndex(cx); + + *lane = unsigned(arg); + return true; +} + +// Look for arguments (ta, idx) where ta is a TypedArray and idx is a +// non-negative integer. +// Check that accessBytes can be accessed starting from index idx in the array. +// Return the array handle in typedArray and idx converted to a byte offset in byteStart. +static bool +TypedArrayFromArgs(JSContext* cx, const CallArgs& args, uint32_t accessBytes, + MutableHandleObject typedArray, size_t* byteStart) +{ + if (!args[0].isObject()) + return ErrorBadArgs(cx); + + JSObject& argobj = args[0].toObject(); + if (!argobj.is()) + return ErrorBadArgs(cx); + + typedArray.set(&argobj); + + uint64_t index; + if (!NonStandardToIndex(cx, args[1], &index)) + return false; + + // Do the range check in 64 bits even when size_t is 32 bits. + // This can't overflow because index <= 2^53. + uint64_t bytes = index * typedArray->as().bytesPerElement(); + // Keep in sync with AsmJS OnOutOfBounds function. + if ((bytes + accessBytes) > typedArray->as().byteLength()) + return ErrorBadIndex(cx); + + *byteStart = bytes; + + return true; +} + +template +static bool +Load(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 2) + return ErrorBadArgs(cx); + + size_t byteStart; + RootedObject typedArray(cx); + if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &typedArray, &byteStart)) + return false; + + Rooted typeDescr(cx, GetTypeDescr(cx)); + if (!typeDescr) + return false; + + Rooted result(cx, TypedObject::createZeroed(cx, typeDescr)); + if (!result) + return false; + + JS::AutoCheckCannotGC nogc(cx); + SharedMem src = + typedArray->as().viewDataEither().addBytes(byteStart).cast(); + Elem* dst = reinterpret_cast(result->typedMem(nogc)); + jit::AtomicOperations::podCopySafeWhenRacy(SharedMem::unshared(dst), src, NumElem); + + args.rval().setObject(*result); + return true; +} + +template +static bool +Store(JSContext* cx, unsigned argc, Value* vp) +{ + typedef typename V::Elem Elem; + + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 3) + return ErrorBadArgs(cx); + + size_t byteStart; + RootedObject typedArray(cx); + if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &typedArray, &byteStart)) + return false; + + if (!IsVectorObject(args[2])) + return ErrorBadArgs(cx); + + JS::AutoCheckCannotGC nogc(cx); + Elem* src = TypedObjectMemory(args[2], nogc); + SharedMem dst = + typedArray->as().viewDataEither().addBytes(byteStart).cast(); + js::jit::AtomicOperations::podCopySafeWhenRacy(dst, SharedMem::unshared(src), NumElem); + + args.rval().setObject(args[2].toObject()); + return true; +} + +#define DEFINE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +FLOAT32X4_FUNCTION_LIST(DEFINE_SIMD_FLOAT32X4_FUNCTION) +#undef DEFINE_SIMD_FLOAT32X4_FUNCTION + +#define DEFINE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +FLOAT64X2_FUNCTION_LIST(DEFINE_SIMD_FLOAT64X2_FUNCTION) +#undef DEFINE_SIMD_FLOAT64X2_FUNCTION + +#define DEFINE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +INT8X16_FUNCTION_LIST(DEFINE_SIMD_INT8X16_FUNCTION) +#undef DEFINE_SIMD_INT8X16_FUNCTION + +#define DEFINE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +INT16X8_FUNCTION_LIST(DEFINE_SIMD_INT16X8_FUNCTION) +#undef DEFINE_SIMD_INT16X8_FUNCTION + +#define DEFINE_SIMD_INT32X4_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +INT32X4_FUNCTION_LIST(DEFINE_SIMD_INT32X4_FUNCTION) +#undef DEFINE_SIMD_INT32X4_FUNCTION + +#define DEFINE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +UINT8X16_FUNCTION_LIST(DEFINE_SIMD_UINT8X16_FUNCTION) +#undef DEFINE_SIMD_UINT8X16_FUNCTION + +#define DEFINE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +UINT16X8_FUNCTION_LIST(DEFINE_SIMD_UINT16X8_FUNCTION) +#undef DEFINE_SIMD_UINT16X8_FUNCTION + +#define DEFINE_SIMD_UINT32X4_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +UINT32X4_FUNCTION_LIST(DEFINE_SIMD_UINT32X4_FUNCTION) +#undef DEFINE_SIMD_UINT32X4_FUNCTION + +#define DEFINE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} + +BOOL8X16_FUNCTION_LIST(DEFINE_SIMD_BOOL8X16_FUNCTION) +#undef DEFINE_SIMD_BOOL8X16_FUNCTION + +#define DEFINE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +BOOL16X8_FUNCTION_LIST(DEFINE_SIMD_BOOL16X8_FUNCTION) +#undef DEFINE_SIMD_BOOL16X8_FUNCTION + +#define DEFINE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +BOOL32X4_FUNCTION_LIST(DEFINE_SIMD_BOOL32X4_FUNCTION) +#undef DEFINE_SIMD_BOOL32X4_FUNCTION + +#define DEFINE_SIMD_BOOL64X2_FUNCTION(Name, Func, Operands) \ +bool \ +js::simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \ +{ \ + return Func(cx, argc, vp); \ +} +BOOL64X2_FUNCTION_LIST(DEFINE_SIMD_BOOL64X2_FUNCTION) +#undef DEFINE_SIMD_BOOL64X2_FUNCTION diff --git a/js/src/builtin/SIMD.h b/js/src/builtin/SIMD.h new file mode 100644 index 000000000000..08b8ce59dc2b --- /dev/null +++ b/js/src/builtin/SIMD.h @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_SIMD_h +#define builtin_SIMD_h + +#include "jsapi.h" +#include "NamespaceImports.h" + +#include "builtin/SIMDConstants.h" +#include "jit/IonTypes.h" +#include "js/Conversions.h" + +/* + * JS SIMD functions. + * Spec matching polyfill: + * https://github.com/tc39/ecmascript_simd/blob/master/src/ecmascript_simd.js + */ + +namespace js { + +class GlobalObject; + +// These classes implement the concept containing the following constraints: +// - requires typename Elem: this is the scalar lane type, stored in each lane +// of the SIMD vector. +// - requires static const unsigned lanes: this is the number of lanes (length) +// of the SIMD vector. +// - requires static const SimdType type: this is the SimdType enum value +// corresponding to the SIMD type. +// - requires static bool Cast(JSContext*, JS::HandleValue, Elem*): casts a +// given Value to the current scalar lane type and saves it in the Elem +// out-param. +// - requires static Value ToValue(Elem): returns a Value of the right type +// containing the given value. +// +// This concept is used in the templates above to define the functions +// associated to a given type and in their implementations, to avoid code +// redundancy. + +struct Float32x4 { + typedef float Elem; + static const unsigned lanes = 4; + static const SimdType type = SimdType::Float32x4; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + double d; + if (!ToNumber(cx, v, &d)) + return false; + *out = float(d); + return true; + } + static Value ToValue(Elem value) { + return DoubleValue(JS::CanonicalizeNaN(value)); + } +}; + +struct Float64x2 { + typedef double Elem; + static const unsigned lanes = 2; + static const SimdType type = SimdType::Float64x2; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToNumber(cx, v, out); + } + static Value ToValue(Elem value) { + return DoubleValue(JS::CanonicalizeNaN(value)); + } +}; + +struct Int8x16 { + typedef int8_t Elem; + static const unsigned lanes = 16; + static const SimdType type = SimdType::Int8x16; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToInt8(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Int16x8 { + typedef int16_t Elem; + static const unsigned lanes = 8; + static const SimdType type = SimdType::Int16x8; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToInt16(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Int32x4 { + typedef int32_t Elem; + static const unsigned lanes = 4; + static const SimdType type = SimdType::Int32x4; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToInt32(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Uint8x16 { + typedef uint8_t Elem; + static const unsigned lanes = 16; + static const SimdType type = SimdType::Uint8x16; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToUint8(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Uint16x8 { + typedef uint16_t Elem; + static const unsigned lanes = 8; + static const SimdType type = SimdType::Uint16x8; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToUint16(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Uint32x4 { + typedef uint32_t Elem; + static const unsigned lanes = 4; + static const SimdType type = SimdType::Uint32x4; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + return ToUint32(cx, v, out); + } + static Value ToValue(Elem value) { + return NumberValue(value); + } +}; + +struct Bool8x16 { + typedef int8_t Elem; + static const unsigned lanes = 16; + static const SimdType type = SimdType::Bool8x16; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + *out = ToBoolean(v) ? -1 : 0; + return true; + } + static Value ToValue(Elem value) { + return BooleanValue(value); + } +}; + +struct Bool16x8 { + typedef int16_t Elem; + static const unsigned lanes = 8; + static const SimdType type = SimdType::Bool16x8; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + *out = ToBoolean(v) ? -1 : 0; + return true; + } + static Value ToValue(Elem value) { + return BooleanValue(value); + } +}; + +struct Bool32x4 { + typedef int32_t Elem; + static const unsigned lanes = 4; + static const SimdType type = SimdType::Bool32x4; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + *out = ToBoolean(v) ? -1 : 0; + return true; + } + static Value ToValue(Elem value) { + return BooleanValue(value); + } +}; + +struct Bool64x2 { + typedef int64_t Elem; + static const unsigned lanes = 2; + static const SimdType type = SimdType::Bool64x2; + static MOZ_MUST_USE bool Cast(JSContext* cx, JS::HandleValue v, Elem* out) { + *out = ToBoolean(v) ? -1 : 0; + return true; + } + static Value ToValue(Elem value) { + return BooleanValue(value); + } +}; + +// Get the well known name of the SIMD.* object corresponding to type. +PropertyName* SimdTypeToName(const JSAtomState& atoms, SimdType type); + +// Check if name is the well known name of a SIMD type. +// Returns true and sets *type iff name is known. +bool IsSimdTypeName(const JSAtomState& atoms, const PropertyName* name, SimdType* type); + +const char* SimdTypeToString(SimdType type); + +template +JSObject* CreateSimd(JSContext* cx, const typename V::Elem* data); + +template +bool IsVectorObject(HandleValue v); + +template +MOZ_MUST_USE bool ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out); + +JSObject* +InitSimdClass(JSContext* cx, Handle global); + +namespace jit { + +extern const JSJitInfo JitInfo_SimdInt32x4_extractLane; +extern const JSJitInfo JitInfo_SimdFloat32x4_extractLane; + +} // namespace jit + +#define DECLARE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp); +FLOAT32X4_FUNCTION_LIST(DECLARE_SIMD_FLOAT32X4_FUNCTION) +#undef DECLARE_SIMD_FLOAT32X4_FUNCTION + +#define DECLARE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp); +FLOAT64X2_FUNCTION_LIST(DECLARE_SIMD_FLOAT64X2_FUNCTION) +#undef DECLARE_SIMD_FLOAT64X2_FUNCTION + +#define DECLARE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp); +INT8X16_FUNCTION_LIST(DECLARE_SIMD_INT8X16_FUNCTION) +#undef DECLARE_SIMD_INT8X16_FUNCTION + +#define DECLARE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp); +INT16X8_FUNCTION_LIST(DECLARE_SIMD_INT16X8_FUNCTION) +#undef DECLARE_SIMD_INT16X8_FUNCTION + +#define DECLARE_SIMD_INT32X4_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp); +INT32X4_FUNCTION_LIST(DECLARE_SIMD_INT32X4_FUNCTION) +#undef DECLARE_SIMD_INT32X4_FUNCTION + +#define DECLARE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp); +UINT8X16_FUNCTION_LIST(DECLARE_SIMD_UINT8X16_FUNCTION) +#undef DECLARE_SIMD_UINT8X16_FUNCTION + +#define DECLARE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp); +UINT16X8_FUNCTION_LIST(DECLARE_SIMD_UINT16X8_FUNCTION) +#undef DECLARE_SIMD_UINT16X8_FUNCTION + +#define DECLARE_SIMD_UINT32X4_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp); +UINT32X4_FUNCTION_LIST(DECLARE_SIMD_UINT32X4_FUNCTION) +#undef DECLARE_SIMD_UINT32X4_FUNCTION + +#define DECLARE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp); +BOOL8X16_FUNCTION_LIST(DECLARE_SIMD_BOOL8X16_FUNCTION) +#undef DECLARE_SIMD_BOOL8X16_FUNCTION + +#define DECLARE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp); +BOOL16X8_FUNCTION_LIST(DECLARE_SIMD_BOOL16X8_FUNCTION) +#undef DECLARE_SIMD_BOOL16X8_FUNCTION + +#define DECLARE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp); +BOOL32X4_FUNCTION_LIST(DECLARE_SIMD_BOOL32X4_FUNCTION) +#undef DECLARE_SIMD_BOOL32X4_FUNCTION + +#define DECLARE_SIMD_BOOL64X2_FUNCTION(Name, Func, Operands) \ +extern MOZ_MUST_USE bool \ +simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp); +BOOL64X2_FUNCTION_LIST(DECLARE_SIMD_BOOL64X2_FUNCTION) +#undef DECLARE_SIMD_BOOL64X2_FUNCTION + +} /* namespace js */ + +#endif /* builtin_SIMD_h */ diff --git a/js/src/builtin/SIMDConstants.h b/js/src/builtin/SIMDConstants.h new file mode 100644 index 000000000000..8513f886fac5 --- /dev/null +++ b/js/src/builtin/SIMDConstants.h @@ -0,0 +1,941 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_SIMDConstants_h +#define builtin_SIMDConstants_h + +#include "mozilla/Assertions.h" + +#include "builtin/TypedObjectConstants.h" + +// Bool8x16. +#define BOOL8X16_UNARY_FUNCTION_LIST(V) \ + V(not, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(allTrue, (AllTrue), 1) \ + V(anyTrue, (AnyTrue), 1) + +#define BOOL8X16_BINARY_FUNCTION_LIST(V) \ + V(extractLane, (ExtractLane), 2) \ + V(and, (BinaryFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(xor, (BinaryFunc), 2) \ + +#define BOOL8X16_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) + +#define BOOL8X16_FUNCTION_LIST(V) \ + BOOL8X16_UNARY_FUNCTION_LIST(V) \ + BOOL8X16_BINARY_FUNCTION_LIST(V) \ + BOOL8X16_TERNARY_FUNCTION_LIST(V) + +// Bool 16x8. +#define BOOL16X8_UNARY_FUNCTION_LIST(V) \ + V(not, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(allTrue, (AllTrue), 1) \ + V(anyTrue, (AnyTrue), 1) + +#define BOOL16X8_BINARY_FUNCTION_LIST(V) \ + V(extractLane, (ExtractLane), 2) \ + V(and, (BinaryFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(xor, (BinaryFunc), 2) \ + +#define BOOL16X8_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) + +#define BOOL16X8_FUNCTION_LIST(V) \ + BOOL16X8_UNARY_FUNCTION_LIST(V) \ + BOOL16X8_BINARY_FUNCTION_LIST(V) \ + BOOL16X8_TERNARY_FUNCTION_LIST(V) + +// Bool32x4. +#define BOOL32X4_UNARY_FUNCTION_LIST(V) \ + V(not, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(allTrue, (AllTrue), 1) \ + V(anyTrue, (AnyTrue), 1) + +#define BOOL32X4_BINARY_FUNCTION_LIST(V) \ + V(extractLane, (ExtractLane), 2) \ + V(and, (BinaryFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(xor, (BinaryFunc), 2) \ + +#define BOOL32X4_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) + +#define BOOL32X4_FUNCTION_LIST(V) \ + BOOL32X4_UNARY_FUNCTION_LIST(V) \ + BOOL32X4_BINARY_FUNCTION_LIST(V) \ + BOOL32X4_TERNARY_FUNCTION_LIST(V) + +// Bool64x2. +#define BOOL64X2_UNARY_FUNCTION_LIST(V) \ + V(not, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(allTrue, (AllTrue), 1) \ + V(anyTrue, (AnyTrue), 1) + +#define BOOL64X2_BINARY_FUNCTION_LIST(V) \ + V(extractLane, (ExtractLane), 2) \ + V(and, (BinaryFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(xor, (BinaryFunc), 2) \ + +#define BOOL64X2_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) + +#define BOOL64X2_FUNCTION_LIST(V) \ + BOOL64X2_UNARY_FUNCTION_LIST(V) \ + BOOL64X2_BINARY_FUNCTION_LIST(V) \ + BOOL64X2_TERNARY_FUNCTION_LIST(V) + +// Float32x4. +#define FLOAT32X4_UNARY_FUNCTION_LIST(V) \ + V(abs, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4, (FuncConvert), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4, (FuncConvert), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(reciprocalApproximation, (UnaryFunc), 1) \ + V(reciprocalSqrtApproximation, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(sqrt, (UnaryFunc), 1) + +#define FLOAT32X4_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(div, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(load3, (Load), 2) \ + V(load2, (Load), 2) \ + V(load1, (Load), 2) \ + V(max, (BinaryFunc), 2) \ + V(maxNum, (BinaryFunc), 2) \ + V(min, (BinaryFunc), 2) \ + V(minNum, (BinaryFunc), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(sub, (BinaryFunc), 2) + +#define FLOAT32X4_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) \ + V(store3, (Store), 3) \ + V(store2, (Store), 3) \ + V(store1, (Store), 3) + +#define FLOAT32X4_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 5) \ + V(shuffle, Shuffle, 6) + +#define FLOAT32X4_FUNCTION_LIST(V) \ + FLOAT32X4_UNARY_FUNCTION_LIST(V) \ + FLOAT32X4_BINARY_FUNCTION_LIST(V) \ + FLOAT32X4_TERNARY_FUNCTION_LIST(V) \ + FLOAT32X4_SHUFFLE_FUNCTION_LIST(V) + +// Float64x2. +#define FLOAT64X2_UNARY_FUNCTION_LIST(V) \ + V(abs, (UnaryFunc), 1) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(reciprocalApproximation, (UnaryFunc), 1) \ + V(reciprocalSqrtApproximation, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) \ + V(sqrt, (UnaryFunc), 1) + +#define FLOAT64X2_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(div, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(load1, (Load), 2) \ + V(max, (BinaryFunc), 2) \ + V(maxNum, (BinaryFunc), 2) \ + V(min, (BinaryFunc), 2) \ + V(minNum, (BinaryFunc), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(sub, (BinaryFunc), 2) + +#define FLOAT64X2_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) \ + V(store1, (Store), 3) + +#define FLOAT64X2_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 3) \ + V(shuffle, Shuffle, 4) + +#define FLOAT64X2_FUNCTION_LIST(V) \ + FLOAT64X2_UNARY_FUNCTION_LIST(V) \ + FLOAT64X2_BINARY_FUNCTION_LIST(V) \ + FLOAT64X2_TERNARY_FUNCTION_LIST(V) \ + FLOAT64X2_SHUFFLE_FUNCTION_LIST(V) + +// Int8x16. +#define INT8X16_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) + +#define INT8X16_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(addSaturate, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(subSaturate, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define INT8X16_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) + +#define INT8X16_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 17) \ + V(shuffle, Shuffle, 18) + +#define INT8X16_FUNCTION_LIST(V) \ + INT8X16_UNARY_FUNCTION_LIST(V) \ + INT8X16_BINARY_FUNCTION_LIST(V) \ + INT8X16_TERNARY_FUNCTION_LIST(V) \ + INT8X16_SHUFFLE_FUNCTION_LIST(V) + +// Uint8x16. +#define UINT8X16_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) + +#define UINT8X16_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(addSaturate, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(subSaturate, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define UINT8X16_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) + +#define UINT8X16_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 17) \ + V(shuffle, Shuffle, 18) + +#define UINT8X16_FUNCTION_LIST(V) \ + UINT8X16_UNARY_FUNCTION_LIST(V) \ + UINT8X16_BINARY_FUNCTION_LIST(V) \ + UINT8X16_TERNARY_FUNCTION_LIST(V) \ + UINT8X16_SHUFFLE_FUNCTION_LIST(V) + +// Int16x8. +#define INT16X8_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) + +#define INT16X8_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(addSaturate, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(subSaturate, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define INT16X8_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) + +#define INT16X8_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 9) \ + V(shuffle, Shuffle, 10) + +#define INT16X8_FUNCTION_LIST(V) \ + INT16X8_UNARY_FUNCTION_LIST(V) \ + INT16X8_BINARY_FUNCTION_LIST(V) \ + INT16X8_TERNARY_FUNCTION_LIST(V) \ + INT16X8_SHUFFLE_FUNCTION_LIST(V) + +// Uint16x8. +#define UINT16X8_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 1) + +#define UINT16X8_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(addSaturate, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(subSaturate, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define UINT16X8_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) + +#define UINT16X8_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 9) \ + V(shuffle, Shuffle, 10) + +#define UINT16X8_FUNCTION_LIST(V) \ + UINT16X8_UNARY_FUNCTION_LIST(V) \ + UINT16X8_BINARY_FUNCTION_LIST(V) \ + UINT16X8_TERNARY_FUNCTION_LIST(V) \ + UINT16X8_SHUFFLE_FUNCTION_LIST(V) + +// Int32x4. +#define INT32X4_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4, (FuncConvert), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(fromUint32x4Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 0) + +#define INT32X4_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(load3, (Load), 2) \ + V(load2, (Load), 2) \ + V(load1, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define INT32X4_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) \ + V(store3, (Store), 3) \ + V(store2, (Store), 3) \ + V(store1, (Store), 3) + +#define INT32X4_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 5) \ + V(shuffle, Shuffle, 6) + +#define INT32X4_FUNCTION_LIST(V) \ + INT32X4_UNARY_FUNCTION_LIST(V) \ + INT32X4_BINARY_FUNCTION_LIST(V) \ + INT32X4_TERNARY_FUNCTION_LIST(V) \ + INT32X4_SHUFFLE_FUNCTION_LIST(V) + +// Uint32x4. +#define UINT32X4_UNARY_FUNCTION_LIST(V) \ + V(check, (UnaryFunc), 1) \ + V(fromFloat32x4, (FuncConvert), 1) \ + V(fromFloat32x4Bits, (FuncConvertBits), 1) \ + V(fromFloat64x2Bits, (FuncConvertBits), 1) \ + V(fromInt8x16Bits, (FuncConvertBits), 1) \ + V(fromInt16x8Bits, (FuncConvertBits), 1) \ + V(fromInt32x4Bits, (FuncConvertBits), 1) \ + V(fromUint8x16Bits, (FuncConvertBits), 1) \ + V(fromUint16x8Bits, (FuncConvertBits), 1) \ + V(neg, (UnaryFunc), 1) \ + V(not, (UnaryFunc), 1) \ + V(splat, (FuncSplat), 0) + +#define UINT32X4_BINARY_FUNCTION_LIST(V) \ + V(add, (BinaryFunc), 2) \ + V(and, (BinaryFunc), 2) \ + V(equal, (CompareFunc), 2) \ + V(extractLane, (ExtractLane), 2) \ + V(greaterThan, (CompareFunc), 2) \ + V(greaterThanOrEqual, (CompareFunc), 2) \ + V(lessThan, (CompareFunc), 2) \ + V(lessThanOrEqual, (CompareFunc), 2) \ + V(load, (Load), 2) \ + V(load3, (Load), 2) \ + V(load2, (Load), 2) \ + V(load1, (Load), 2) \ + V(mul, (BinaryFunc), 2) \ + V(notEqual, (CompareFunc), 2) \ + V(or, (BinaryFunc), 2) \ + V(sub, (BinaryFunc), 2) \ + V(shiftLeftByScalar, (BinaryScalar), 2) \ + V(shiftRightByScalar, (BinaryScalar), 2) \ + V(xor, (BinaryFunc), 2) + +#define UINT32X4_TERNARY_FUNCTION_LIST(V) \ + V(replaceLane, (ReplaceLane), 3) \ + V(select, (Select), 3) \ + V(store, (Store), 3) \ + V(store3, (Store), 3) \ + V(store2, (Store), 3) \ + V(store1, (Store), 3) + +#define UINT32X4_SHUFFLE_FUNCTION_LIST(V) \ + V(swizzle, Swizzle, 5) \ + V(shuffle, Shuffle, 6) + +#define UINT32X4_FUNCTION_LIST(V) \ + UINT32X4_UNARY_FUNCTION_LIST(V) \ + UINT32X4_BINARY_FUNCTION_LIST(V) \ + UINT32X4_TERNARY_FUNCTION_LIST(V) \ + UINT32X4_SHUFFLE_FUNCTION_LIST(V) + +/* + * The FOREACH macros below partition all of the SIMD operations into disjoint + * sets. + */ + +// Operations available on all SIMD types. Mixed arity. +#define FOREACH_COMMON_SIMD_OP(_) \ + _(extractLane) \ + _(replaceLane) \ + _(check) \ + _(splat) + +// Lanewise operations available on numeric SIMD types. +// Include lane-wise select here since it is not arithmetic and defined on +// numeric types too. +#define FOREACH_LANE_SIMD_OP(_) \ + _(select) \ + _(swizzle) \ + _(shuffle) + +// Memory operations available on numeric SIMD types. +#define FOREACH_MEMORY_SIMD_OP(_) \ + _(load) \ + _(store) + +// Memory operations available on numeric X4 SIMD types. +#define FOREACH_MEMORY_X4_SIMD_OP(_) \ + _(load1) \ + _(load2) \ + _(load3) \ + _(store1) \ + _(store2) \ + _(store3) + +// Unary operations on Bool vectors. +#define FOREACH_BOOL_SIMD_UNOP(_) \ + _(allTrue) \ + _(anyTrue) + +// Unary bitwise SIMD operators defined on all integer and boolean SIMD types. +#define FOREACH_BITWISE_SIMD_UNOP(_) \ + _(not) + +// Binary bitwise SIMD operators defined on all integer and boolean SIMD types. +#define FOREACH_BITWISE_SIMD_BINOP(_) \ + _(and) \ + _(or) \ + _(xor) + +// Bitwise shifts defined on integer SIMD types. +#define FOREACH_SHIFT_SIMD_OP(_) \ + _(shiftLeftByScalar) \ + _(shiftRightByScalar) + +// Unary arithmetic operators defined on numeric SIMD types. +#define FOREACH_NUMERIC_SIMD_UNOP(_) \ + _(neg) + +// Binary arithmetic operators defined on numeric SIMD types. +#define FOREACH_NUMERIC_SIMD_BINOP(_) \ + _(add) \ + _(sub) \ + _(mul) + +// Unary arithmetic operators defined on floating point SIMD types. +#define FOREACH_FLOAT_SIMD_UNOP(_) \ + _(abs) \ + _(sqrt) \ + _(reciprocalApproximation) \ + _(reciprocalSqrtApproximation) + +// Binary arithmetic operators defined on floating point SIMD types. +#define FOREACH_FLOAT_SIMD_BINOP(_) \ + _(div) \ + _(max) \ + _(min) \ + _(maxNum) \ + _(minNum) + +// Binary operations on small integer (< 32 bits) vectors. +#define FOREACH_SMINT_SIMD_BINOP(_) \ + _(addSaturate) \ + _(subSaturate) + +// Comparison operators defined on numeric SIMD types. +#define FOREACH_COMP_SIMD_OP(_) \ + _(lessThan) \ + _(lessThanOrEqual) \ + _(equal) \ + _(notEqual) \ + _(greaterThan) \ + _(greaterThanOrEqual) + +/* + * All SIMD operations, excluding casts. + */ +#define FORALL_SIMD_NONCAST_OP(_) \ + FOREACH_COMMON_SIMD_OP(_) \ + FOREACH_LANE_SIMD_OP(_) \ + FOREACH_MEMORY_SIMD_OP(_) \ + FOREACH_MEMORY_X4_SIMD_OP(_) \ + FOREACH_BOOL_SIMD_UNOP(_) \ + FOREACH_BITWISE_SIMD_UNOP(_) \ + FOREACH_BITWISE_SIMD_BINOP(_) \ + FOREACH_SHIFT_SIMD_OP(_) \ + FOREACH_NUMERIC_SIMD_UNOP(_) \ + FOREACH_NUMERIC_SIMD_BINOP(_) \ + FOREACH_FLOAT_SIMD_UNOP(_) \ + FOREACH_FLOAT_SIMD_BINOP(_) \ + FOREACH_SMINT_SIMD_BINOP(_) \ + FOREACH_COMP_SIMD_OP(_) + +/* + * All operations on integer SIMD types, excluding casts and + * FOREACH_MEMORY_X4_OP. + */ +#define FORALL_INT_SIMD_OP(_) \ + FOREACH_COMMON_SIMD_OP(_) \ + FOREACH_LANE_SIMD_OP(_) \ + FOREACH_MEMORY_SIMD_OP(_) \ + FOREACH_BITWISE_SIMD_UNOP(_) \ + FOREACH_BITWISE_SIMD_BINOP(_) \ + FOREACH_SHIFT_SIMD_OP(_) \ + FOREACH_NUMERIC_SIMD_UNOP(_) \ + FOREACH_NUMERIC_SIMD_BINOP(_) \ + FOREACH_COMP_SIMD_OP(_) + +/* + * All operations on floating point SIMD types, excluding casts and + * FOREACH_MEMORY_X4_OP. + */ +#define FORALL_FLOAT_SIMD_OP(_) \ + FOREACH_COMMON_SIMD_OP(_) \ + FOREACH_LANE_SIMD_OP(_) \ + FOREACH_MEMORY_SIMD_OP(_) \ + FOREACH_NUMERIC_SIMD_UNOP(_) \ + FOREACH_NUMERIC_SIMD_BINOP(_) \ + FOREACH_FLOAT_SIMD_UNOP(_) \ + FOREACH_FLOAT_SIMD_BINOP(_) \ + FOREACH_COMP_SIMD_OP(_) + +/* + * All operations on Bool SIMD types. + * + * These types don't have casts, so no need to specialize. + */ +#define FORALL_BOOL_SIMD_OP(_) \ + FOREACH_COMMON_SIMD_OP(_) \ + FOREACH_BOOL_SIMD_UNOP(_) \ + FOREACH_BITWISE_SIMD_UNOP(_) \ + FOREACH_BITWISE_SIMD_BINOP(_) + +/* + * The sets of cast operations are listed per type below. + * + * These sets are not disjoint. + */ + +#define FOREACH_INT8X16_SIMD_CAST(_) \ + _(fromFloat32x4Bits) \ + _(fromFloat64x2Bits) \ + _(fromInt16x8Bits) \ + _(fromInt32x4Bits) + +#define FOREACH_INT16X8_SIMD_CAST(_) \ + _(fromFloat32x4Bits) \ + _(fromFloat64x2Bits) \ + _(fromInt8x16Bits) \ + _(fromInt32x4Bits) + +#define FOREACH_INT32X4_SIMD_CAST(_) \ + _(fromFloat32x4) \ + _(fromFloat32x4Bits) \ + _(fromFloat64x2Bits) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) + +#define FOREACH_FLOAT32X4_SIMD_CAST(_)\ + _(fromFloat64x2Bits) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) \ + _(fromInt32x4) \ + _(fromInt32x4Bits) + +#define FOREACH_FLOAT64X2_SIMD_CAST(_)\ + _(fromFloat32x4Bits) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) \ + _(fromInt32x4Bits) + +// All operations on Int32x4. +#define FORALL_INT32X4_SIMD_OP(_) \ + FORALL_INT_SIMD_OP(_) \ + FOREACH_MEMORY_X4_SIMD_OP(_) \ + FOREACH_INT32X4_SIMD_CAST(_) + +// All operations on Float32X4 +#define FORALL_FLOAT32X4_SIMD_OP(_) \ + FORALL_FLOAT_SIMD_OP(_) \ + FOREACH_MEMORY_X4_SIMD_OP(_) \ + FOREACH_FLOAT32X4_SIMD_CAST(_) + +/* + * All SIMD operations assuming only 32x4 types exist. + * This is used in the current asm.js impl. + */ +#define FORALL_SIMD_ASMJS_OP(_) \ + FORALL_SIMD_NONCAST_OP(_) \ + _(fromFloat32x4) \ + _(fromFloat32x4Bits) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) \ + _(fromInt32x4) \ + _(fromInt32x4Bits) \ + _(fromUint8x16Bits) \ + _(fromUint16x8Bits) \ + _(fromUint32x4) \ + _(fromUint32x4Bits) + +// All operations on Int8x16 or Uint8x16 in the asm.js world. +// Note: this does not include conversions and casts to/from Uint8x16 because +// this list is shared between Int8x16 and Uint8x16. +#define FORALL_INT8X16_ASMJS_OP(_) \ + FORALL_INT_SIMD_OP(_) \ + FOREACH_SMINT_SIMD_BINOP(_) \ + _(fromInt16x8Bits) \ + _(fromInt32x4Bits) \ + _(fromFloat32x4Bits) + +// All operations on Int16x8 or Uint16x8 in the asm.js world. +// Note: this does not include conversions and casts to/from Uint16x8 because +// this list is shared between Int16x8 and Uint16x8. +#define FORALL_INT16X8_ASMJS_OP(_) \ + FORALL_INT_SIMD_OP(_) \ + FOREACH_SMINT_SIMD_BINOP(_) \ + _(fromInt8x16Bits) \ + _(fromInt32x4Bits) \ + _(fromFloat32x4Bits) + +// All operations on Int32x4 or Uint32x4 in the asm.js world. +// Note: this does not include conversions and casts to/from Uint32x4 because +// this list is shared between Int32x4 and Uint32x4. +#define FORALL_INT32X4_ASMJS_OP(_) \ + FORALL_INT_SIMD_OP(_) \ + FOREACH_MEMORY_X4_SIMD_OP(_) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) \ + _(fromFloat32x4) \ + _(fromFloat32x4Bits) + +// All operations on Float32X4 in the asm.js world. +#define FORALL_FLOAT32X4_ASMJS_OP(_) \ + FORALL_FLOAT_SIMD_OP(_) \ + FOREACH_MEMORY_X4_SIMD_OP(_) \ + _(fromInt8x16Bits) \ + _(fromInt16x8Bits) \ + _(fromInt32x4Bits) \ + _(fromInt32x4) \ + _(fromUint32x4) + +namespace js { + +// Complete set of SIMD types. +// It must be kept in sync with the enumeration of values in +// TypedObjectConstants.h; in particular we need to ensure that Count is +// appropriately set with respect to the number of actual types. +enum class SimdType { + Int8x16 = JS_SIMDTYPEREPR_INT8X16, + Int16x8 = JS_SIMDTYPEREPR_INT16X8, + Int32x4 = JS_SIMDTYPEREPR_INT32X4, + Uint8x16 = JS_SIMDTYPEREPR_UINT8X16, + Uint16x8 = JS_SIMDTYPEREPR_UINT16X8, + Uint32x4 = JS_SIMDTYPEREPR_UINT32X4, + Float32x4 = JS_SIMDTYPEREPR_FLOAT32X4, + Float64x2 = JS_SIMDTYPEREPR_FLOAT64X2, + Bool8x16 = JS_SIMDTYPEREPR_BOOL8X16, + Bool16x8 = JS_SIMDTYPEREPR_BOOL16X8, + Bool32x4 = JS_SIMDTYPEREPR_BOOL32X4, + Bool64x2 = JS_SIMDTYPEREPR_BOOL64X2, + Count +}; + +// The integer SIMD types have a lot of operations that do the exact same thing +// for signed and unsigned integer types. Sometimes it is simpler to treat +// signed and unsigned integer SIMD types as the same type, using a SimdSign to +// distinguish the few cases where there is a difference. +enum class SimdSign { + // Signedness is not applicable to this type. (i.e., Float or Bool). + NotApplicable, + // Treat as an unsigned integer with a range 0 .. 2^N-1. + Unsigned, + // Treat as a signed integer in two's complement encoding. + Signed, +}; + +// Get the signedness of a SIMD type. +inline SimdSign +GetSimdSign(SimdType t) +{ + switch(t) { + case SimdType::Int8x16: + case SimdType::Int16x8: + case SimdType::Int32x4: + return SimdSign::Signed; + + case SimdType::Uint8x16: + case SimdType::Uint16x8: + case SimdType::Uint32x4: + return SimdSign::Unsigned; + + default: + return SimdSign::NotApplicable; + } +} + +inline bool +IsSignedIntSimdType(SimdType type) +{ + return GetSimdSign(type) == SimdSign::Signed; +} + +// Get the boolean SIMD type with the same shape as t. +// +// This is the result type of a comparison operation, and it can also be used to +// identify the geometry of a SIMD type. +inline SimdType +GetBooleanSimdType(SimdType t) +{ + switch(t) { + case SimdType::Int8x16: + case SimdType::Uint8x16: + case SimdType::Bool8x16: + return SimdType::Bool8x16; + + case SimdType::Int16x8: + case SimdType::Uint16x8: + case SimdType::Bool16x8: + return SimdType::Bool16x8; + + case SimdType::Int32x4: + case SimdType::Uint32x4: + case SimdType::Float32x4: + case SimdType::Bool32x4: + return SimdType::Bool32x4; + + case SimdType::Float64x2: + case SimdType::Bool64x2: + return SimdType::Bool64x2; + + case SimdType::Count: + break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad SIMD type"); +} + +// Get the number of lanes in a SIMD type. +inline unsigned +GetSimdLanes(SimdType t) +{ + switch(t) { + case SimdType::Int8x16: + case SimdType::Uint8x16: + case SimdType::Bool8x16: + return 16; + + case SimdType::Int16x8: + case SimdType::Uint16x8: + case SimdType::Bool16x8: + return 8; + + case SimdType::Int32x4: + case SimdType::Uint32x4: + case SimdType::Float32x4: + case SimdType::Bool32x4: + return 4; + + case SimdType::Float64x2: + case SimdType::Bool64x2: + return 2; + + case SimdType::Count: + break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad SIMD type"); +} + +// Complete set of SIMD operations. +// +// No SIMD types implement all of these operations. +// +// C++ defines keywords and/or/xor/not, so prepend Fn_ to all named functions to +// avoid clashes. +// +// Note: because of a gcc < v4.8's compiler bug, uint8_t can't be used as the +// storage class here. See bug 1243810. See also +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64037 . +enum class SimdOperation { + // The constructor call. No Fn_ prefix here. + Constructor, + + // All the operations, except for casts. +#define DEFOP(x) Fn_##x, + FORALL_SIMD_NONCAST_OP(DEFOP) +#undef DEFOP + + // Int <-> Float conversions. + Fn_fromInt32x4, + Fn_fromUint32x4, + Fn_fromFloat32x4, + + // Bitcasts. One for each type with a memory representation. + Fn_fromInt8x16Bits, + Fn_fromInt16x8Bits, + Fn_fromInt32x4Bits, + Fn_fromUint8x16Bits, + Fn_fromUint16x8Bits, + Fn_fromUint32x4Bits, + Fn_fromFloat32x4Bits, + Fn_fromFloat64x2Bits, + + Last = Fn_fromFloat64x2Bits +}; + +} // namespace js + +#endif /* builtin_SIMDConstants_h */ diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 2085ccfa12fc..0d26f2fa3a20 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -3980,7 +3980,12 @@ static bool IsSimdAvailable(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - args.rval().set(BooleanValue(cx->jitSupportsSimd())); +#if defined(JS_CODEGEN_NONE) || !defined(ENABLE_SIMD) + bool available = false; +#else + bool available = cx->jitSupportsSimd(); +#endif + args.rval().set(BooleanValue(available)); return true; } diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index cdb2be664247..4399480432e9 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -11,6 +11,7 @@ #include "jsutil.h" +#include "builtin/SIMDConstants.h" #include "gc/Marking.h" #include "js/Vector.h" #include "util/StringBuffer.h" @@ -243,6 +244,10 @@ ScalarTypeDescr::typeName(Type type) JS_FOR_EACH_SCALAR_TYPE_REPR(NUMERIC_TYPE_TO_STRING) #undef NUMERIC_TYPE_TO_STRING case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } @@ -280,6 +285,10 @@ ScalarTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) JS_FOR_EACH_SCALAR_TYPE_REPR(SCALARTYPE_CALL) #undef SCALARTYPE_CALL case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: MOZ_CRASH(); } @@ -392,6 +401,50 @@ js::ReferenceTypeDescr::call(JSContext* cx, unsigned argc, Value* vp) MOZ_CRASH("Unhandled Reference type"); } +/*************************************************************************** + * SIMD type objects + * + * Note: these are partially defined in SIMD.cpp + */ + +SimdType +SimdTypeDescr::type() const { + uint32_t t = uint32_t(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32()); + MOZ_ASSERT(t < uint32_t(SimdType::Count)); + return SimdType(t); +} + +uint32_t +SimdTypeDescr::size(SimdType t) +{ + MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count)); + switch (t) { + case SimdType::Int8x16: + case SimdType::Int16x8: + case SimdType::Int32x4: + case SimdType::Uint8x16: + case SimdType::Uint16x8: + case SimdType::Uint32x4: + case SimdType::Float32x4: + case SimdType::Float64x2: + case SimdType::Bool8x16: + case SimdType::Bool16x8: + case SimdType::Bool32x4: + case SimdType::Bool64x2: + return 16; + case SimdType::Count: + break; + } + MOZ_CRASH("unexpected SIMD type"); +} + +uint32_t +SimdTypeDescr::alignment(SimdType t) +{ + MOZ_ASSERT(unsigned(t) < unsigned(SimdType::Count)); + return size(t); +} + /*************************************************************************** * ArrayMetaTypeDescr class */ @@ -1613,6 +1666,7 @@ TypeDescr::hasProperty(const JSAtomState& names, jsid id) switch (kind()) { case type::Scalar: case type::Reference: + case type::Simd: return false; case type::Array: @@ -1685,6 +1739,7 @@ TypedObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: + case type::Simd: break; case type::Array: { @@ -1736,6 +1791,9 @@ TypedObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiv case type::Reference: break; + case type::Simd: + break; + case type::Array: if (JSID_IS_ATOM(id, cx->names().length)) { if (!typedObj->isAttached()) { @@ -1782,6 +1840,7 @@ TypedObject::obj_getElement(JSContext* cx, HandleObject obj, HandleValue receive switch (descr->kind()) { case type::Scalar: case type::Reference: + case type::Simd: case type::Struct: break; @@ -1827,6 +1886,9 @@ TypedObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, Handl case type::Reference: break; + case type::Simd: + break; + case type::Array: { if (JSID_IS_ATOM(id, cx->names().length)) { if (receiver.isObject() && obj == &receiver.toObject()) { @@ -1894,6 +1956,7 @@ TypedObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, Handl switch (descr->kind()) { case type::Scalar: case type::Reference: + case type::Simd: break; case type::Array: @@ -1947,6 +2010,7 @@ IsOwnId(JSContext* cx, HandleObject obj, HandleId id) switch (typedObj->typeDescr().kind()) { case type::Scalar: case type::Reference: + case type::Simd: return false; case type::Array: @@ -1985,7 +2049,8 @@ TypedObject::obj_newEnumerate(JSContext* cx, HandleObject obj, AutoIdVector& pro RootedId id(cx); switch (descr->kind()) { case type::Scalar: - case type::Reference: { + case type::Reference: + case type::Simd: { // Nothing to enumerate. break; } @@ -2470,6 +2535,22 @@ js::GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp) return true; } +bool +js::GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isInt32()); + // One of the JS_SIMDTYPEREPR_* constants / a SimdType enum value. + // getOrCreateSimdTypeDescr() will do the range check. + int32_t simdTypeRepr = args[0].toInt32(); + Rooted global(cx, cx->global()); + MOZ_ASSERT(global); + auto* obj = GlobalObject::getOrCreateSimdTypeDescr(cx, global, SimdType(simdTypeRepr)); + args.rval().setObject(*obj); + return true; +} + #define JS_STORE_SCALAR_CLASS_IMPL(_constant, T, _name) \ bool \ js::StoreScalar##T::Func(JSContext* cx, unsigned argc, Value* vp) \ @@ -2660,6 +2741,7 @@ visitReferences(TypeDescr& descr, switch (descr.kind()) { case type::Scalar: + case type::Simd: return; case type::Reference: diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 9f254e19a680..7999b3a46c5c 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -121,6 +121,7 @@ namespace type { enum Kind { Scalar = JS_TYPEREPR_SCALAR_KIND, Reference = JS_TYPEREPR_REFERENCE_KIND, + Simd = JS_TYPEREPR_SIMD_KIND, Struct = JS_TYPEREPR_STRUCT_KIND, Array = JS_TYPEREPR_ARRAY_KIND }; @@ -132,6 +133,7 @@ enum Kind { class SimpleTypeDescr; class ComplexTypeDescr; +class SimdTypeDescr; class StructTypeDescr; class TypedProto; @@ -253,6 +255,14 @@ class ScalarTypeDescr : public SimpleTypeDescr "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Uint8Clamped == JS_SCALARTYPEREPR_UINT8_CLAMPED, "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::Float32x4 == JS_SCALARTYPEREPR_FLOAT32X4, + "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::Int8x16 == JS_SCALARTYPEREPR_INT8X16, + "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::Int16x8 == JS_SCALARTYPEREPR_INT16X8, + "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::Int32x4 == JS_SCALARTYPEREPR_INT32X4, + "TypedObjectConstants.h must be consistent with Scalar::Type"); return Type(getReservedSlot(JS_DESCR_SLOT_TYPE).toInt32()); } @@ -330,6 +340,25 @@ class ComplexTypeDescr : public TypeDescr } }; +enum class SimdType; + +/* + * SIMD Type descriptors. + */ +class SimdTypeDescr : public ComplexTypeDescr +{ + public: + static const type::Kind Kind = type::Simd; + static const bool Opaque = false; + static const Class class_; + static uint32_t size(SimdType t); + static uint32_t alignment(SimdType t); + static MOZ_MUST_USE bool call(JSContext* cx, unsigned argc, Value* vp); + static bool is(const Value& v); + + SimdType type() const; +}; + bool IsTypedObjectClass(const Class* clasp); // Defined below bool IsTypedObjectArray(JSObject& obj); @@ -765,6 +794,16 @@ class InlineOpaqueTypedObject : public InlineTypedObject static const Class class_; }; +// Class for the global SIMD object. +class SimdObject : public NativeObject +{ + public: + static const Class class_; + static MOZ_MUST_USE bool toString(JSContext* cx, unsigned int argc, Value* vp); + static MOZ_MUST_USE bool resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId, + bool* resolved); +}; + /* * Usage: NewOpaqueTypedObject(typeObj) * @@ -862,6 +901,16 @@ MOZ_MUST_USE bool ClampToUint8(JSContext* cx, unsigned argc, Value* vp); */ MOZ_MUST_USE bool GetTypedObjectModule(JSContext* cx, unsigned argc, Value* vp); +/* + * Usage: GetSimdTypeDescr(simdTypeRepr) + * + * Returns one of the SIMD type objects, identified by `simdTypeRepr` which must + * be one of the JS_SIMDTYPEREPR_* constants. + * + * The SIMD pseudo-module must have been initialized for this to be safe. + */ +MOZ_MUST_USE bool GetSimdTypeDescr(JSContext* cx, unsigned argc, Value* vp); + /* * Usage: Store_int8(targetDatum, targetOffset, value) * ... @@ -996,7 +1045,8 @@ inline bool IsComplexTypeDescrClass(const Class* clasp) { return clasp == &StructTypeDescr::class_ || - clasp == &ArrayTypeDescr::class_; + clasp == &ArrayTypeDescr::class_ || + clasp == &SimdTypeDescr::class_; } inline bool diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js index 5aaa6b427d2b..33b6319ad2fa 100644 --- a/js/src/builtin/TypedObject.js +++ b/js/src/builtin/TypedObject.js @@ -53,6 +53,9 @@ function TypedObjectGet(descr, typedObj, offset) { case JS_TYPEREPR_REFERENCE_KIND: return TypedObjectGetReference(descr, typedObj, offset); + case JS_TYPEREPR_SIMD_KIND: + return TypedObjectGetSimd(descr, typedObj, offset); + case JS_TYPEREPR_ARRAY_KIND: case JS_TYPEREPR_STRUCT_KIND: return TypedObjectGetDerived(descr, typedObj, offset); @@ -134,6 +137,144 @@ function TypedObjectGetReference(descr, typedObj, offset) { return undefined; } +function TypedObjectGetSimd(descr, typedObj, offset) { + var type = DESCR_TYPE(descr); + var simdTypeDescr = GetSimdTypeDescr(type); + switch (type) { + case JS_SIMDTYPEREPR_FLOAT32X4: + var x = Load_float32(typedObj, offset + 0); + var y = Load_float32(typedObj, offset + 4); + var z = Load_float32(typedObj, offset + 8); + var w = Load_float32(typedObj, offset + 12); + return simdTypeDescr(x, y, z, w); + + case JS_SIMDTYPEREPR_FLOAT64X2: + var x = Load_float64(typedObj, offset + 0); + var y = Load_float64(typedObj, offset + 8); + return simdTypeDescr(x, y); + + case JS_SIMDTYPEREPR_INT8X16: + var s0 = Load_int8(typedObj, offset + 0); + var s1 = Load_int8(typedObj, offset + 1); + var s2 = Load_int8(typedObj, offset + 2); + var s3 = Load_int8(typedObj, offset + 3); + var s4 = Load_int8(typedObj, offset + 4); + var s5 = Load_int8(typedObj, offset + 5); + var s6 = Load_int8(typedObj, offset + 6); + var s7 = Load_int8(typedObj, offset + 7); + var s8 = Load_int8(typedObj, offset + 8); + var s9 = Load_int8(typedObj, offset + 9); + var s10 = Load_int8(typedObj, offset + 10); + var s11 = Load_int8(typedObj, offset + 11); + var s12 = Load_int8(typedObj, offset + 12); + var s13 = Load_int8(typedObj, offset + 13); + var s14 = Load_int8(typedObj, offset + 14); + var s15 = Load_int8(typedObj, offset + 15); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15); + + case JS_SIMDTYPEREPR_INT16X8: + var s0 = Load_int16(typedObj, offset + 0); + var s1 = Load_int16(typedObj, offset + 2); + var s2 = Load_int16(typedObj, offset + 4); + var s3 = Load_int16(typedObj, offset + 6); + var s4 = Load_int16(typedObj, offset + 8); + var s5 = Load_int16(typedObj, offset + 10); + var s6 = Load_int16(typedObj, offset + 12); + var s7 = Load_int16(typedObj, offset + 14); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7); + + case JS_SIMDTYPEREPR_INT32X4: + var x = Load_int32(typedObj, offset + 0); + var y = Load_int32(typedObj, offset + 4); + var z = Load_int32(typedObj, offset + 8); + var w = Load_int32(typedObj, offset + 12); + return simdTypeDescr(x, y, z, w); + + case JS_SIMDTYPEREPR_UINT8X16: + var s0 = Load_uint8(typedObj, offset + 0); + var s1 = Load_uint8(typedObj, offset + 1); + var s2 = Load_uint8(typedObj, offset + 2); + var s3 = Load_uint8(typedObj, offset + 3); + var s4 = Load_uint8(typedObj, offset + 4); + var s5 = Load_uint8(typedObj, offset + 5); + var s6 = Load_uint8(typedObj, offset + 6); + var s7 = Load_uint8(typedObj, offset + 7); + var s8 = Load_uint8(typedObj, offset + 8); + var s9 = Load_uint8(typedObj, offset + 9); + var s10 = Load_uint8(typedObj, offset + 10); + var s11 = Load_uint8(typedObj, offset + 11); + var s12 = Load_uint8(typedObj, offset + 12); + var s13 = Load_uint8(typedObj, offset + 13); + var s14 = Load_uint8(typedObj, offset + 14); + var s15 = Load_uint8(typedObj, offset + 15); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15); + + case JS_SIMDTYPEREPR_UINT16X8: + var s0 = Load_uint16(typedObj, offset + 0); + var s1 = Load_uint16(typedObj, offset + 2); + var s2 = Load_uint16(typedObj, offset + 4); + var s3 = Load_uint16(typedObj, offset + 6); + var s4 = Load_uint16(typedObj, offset + 8); + var s5 = Load_uint16(typedObj, offset + 10); + var s6 = Load_uint16(typedObj, offset + 12); + var s7 = Load_uint16(typedObj, offset + 14); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7); + + case JS_SIMDTYPEREPR_UINT32X4: + var x = Load_uint32(typedObj, offset + 0); + var y = Load_uint32(typedObj, offset + 4); + var z = Load_uint32(typedObj, offset + 8); + var w = Load_uint32(typedObj, offset + 12); + return simdTypeDescr(x, y, z, w); + + case JS_SIMDTYPEREPR_BOOL8X16: + var s0 = Load_int8(typedObj, offset + 0); + var s1 = Load_int8(typedObj, offset + 1); + var s2 = Load_int8(typedObj, offset + 2); + var s3 = Load_int8(typedObj, offset + 3); + var s4 = Load_int8(typedObj, offset + 4); + var s5 = Load_int8(typedObj, offset + 5); + var s6 = Load_int8(typedObj, offset + 6); + var s7 = Load_int8(typedObj, offset + 7); + var s8 = Load_int8(typedObj, offset + 8); + var s9 = Load_int8(typedObj, offset + 9); + var s10 = Load_int8(typedObj, offset + 10); + var s11 = Load_int8(typedObj, offset + 11); + var s12 = Load_int8(typedObj, offset + 12); + var s13 = Load_int8(typedObj, offset + 13); + var s14 = Load_int8(typedObj, offset + 14); + var s15 = Load_int8(typedObj, offset + 15); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15); + + case JS_SIMDTYPEREPR_BOOL16X8: + var s0 = Load_int16(typedObj, offset + 0); + var s1 = Load_int16(typedObj, offset + 2); + var s2 = Load_int16(typedObj, offset + 4); + var s3 = Load_int16(typedObj, offset + 6); + var s4 = Load_int16(typedObj, offset + 8); + var s5 = Load_int16(typedObj, offset + 10); + var s6 = Load_int16(typedObj, offset + 12); + var s7 = Load_int16(typedObj, offset + 14); + return simdTypeDescr(s0, s1, s2, s3, s4, s5, s6, s7); + + case JS_SIMDTYPEREPR_BOOL32X4: + var x = Load_int32(typedObj, offset + 0); + var y = Load_int32(typedObj, offset + 4); + var z = Load_int32(typedObj, offset + 8); + var w = Load_int32(typedObj, offset + 12); + return simdTypeDescr(x, y, z, w); + + case JS_SIMDTYPEREPR_BOOL64X2: + var x = Load_int32(typedObj, offset + 0); + var y = Load_int32(typedObj, offset + 8); + return simdTypeDescr(x, y); + + } + + assert(false, "Unhandled SIMD type: " + type); + return undefined; +} + /////////////////////////////////////////////////////////////////////////// // Setting values // @@ -155,6 +296,10 @@ function TypedObjectSet(descr, typedObj, offset, name, fromValue) { TypedObjectSetReference(descr, typedObj, offset, name, fromValue); return; + case JS_TYPEREPR_SIMD_KIND: + TypedObjectSetSimd(descr, typedObj, offset, fromValue); + return; + case JS_TYPEREPR_ARRAY_KIND: var length = DESCR_ARRAY_LENGTH(descr); if (TypedObjectSetArray(descr, length, typedObj, offset, fromValue)) @@ -269,6 +414,106 @@ function TypedObjectSetReference(descr, typedObj, offset, name, fromValue) { } // Sets `fromValue` to `this` assuming that `this` is a scalar type. +function TypedObjectSetSimd(descr, typedObj, offset, fromValue) { + if (!IsObject(fromValue) || !ObjectIsTypedObject(fromValue)) + ThrowTypeError(JSMSG_CANT_CONVERT_TO, + typeof(fromValue), + DESCR_STRING_REPR(descr)); + + if (!DescrsEquiv(descr, TypedObjectTypeDescr(fromValue))) + ThrowTypeError(JSMSG_CANT_CONVERT_TO, + typeof(fromValue), + DESCR_STRING_REPR(descr)); + + var type = DESCR_TYPE(descr); + switch (type) { + case JS_SIMDTYPEREPR_FLOAT32X4: + Store_float32(typedObj, offset + 0, Load_float32(fromValue, 0)); + Store_float32(typedObj, offset + 4, Load_float32(fromValue, 4)); + Store_float32(typedObj, offset + 8, Load_float32(fromValue, 8)); + Store_float32(typedObj, offset + 12, Load_float32(fromValue, 12)); + break; + case JS_SIMDTYPEREPR_FLOAT64X2: + Store_float64(typedObj, offset + 0, Load_float64(fromValue, 0)); + Store_float64(typedObj, offset + 8, Load_float64(fromValue, 8)); + break; + case JS_SIMDTYPEREPR_INT8X16: + case JS_SIMDTYPEREPR_BOOL8X16: + Store_int8(typedObj, offset + 0, Load_int8(fromValue, 0)); + Store_int8(typedObj, offset + 1, Load_int8(fromValue, 1)); + Store_int8(typedObj, offset + 2, Load_int8(fromValue, 2)); + Store_int8(typedObj, offset + 3, Load_int8(fromValue, 3)); + Store_int8(typedObj, offset + 4, Load_int8(fromValue, 4)); + Store_int8(typedObj, offset + 5, Load_int8(fromValue, 5)); + Store_int8(typedObj, offset + 6, Load_int8(fromValue, 6)); + Store_int8(typedObj, offset + 7, Load_int8(fromValue, 7)); + Store_int8(typedObj, offset + 8, Load_int8(fromValue, 8)); + Store_int8(typedObj, offset + 9, Load_int8(fromValue, 9)); + Store_int8(typedObj, offset + 10, Load_int8(fromValue, 10)); + Store_int8(typedObj, offset + 11, Load_int8(fromValue, 11)); + Store_int8(typedObj, offset + 12, Load_int8(fromValue, 12)); + Store_int8(typedObj, offset + 13, Load_int8(fromValue, 13)); + Store_int8(typedObj, offset + 14, Load_int8(fromValue, 14)); + Store_int8(typedObj, offset + 15, Load_int8(fromValue, 15)); + break; + case JS_SIMDTYPEREPR_INT16X8: + case JS_SIMDTYPEREPR_BOOL16X8: + Store_int16(typedObj, offset + 0, Load_int16(fromValue, 0)); + Store_int16(typedObj, offset + 2, Load_int16(fromValue, 2)); + Store_int16(typedObj, offset + 4, Load_int16(fromValue, 4)); + Store_int16(typedObj, offset + 6, Load_int16(fromValue, 6)); + Store_int16(typedObj, offset + 8, Load_int16(fromValue, 8)); + Store_int16(typedObj, offset + 10, Load_int16(fromValue, 10)); + Store_int16(typedObj, offset + 12, Load_int16(fromValue, 12)); + Store_int16(typedObj, offset + 14, Load_int16(fromValue, 14)); + break; + case JS_SIMDTYPEREPR_INT32X4: + case JS_SIMDTYPEREPR_BOOL32X4: + case JS_SIMDTYPEREPR_BOOL64X2: + Store_int32(typedObj, offset + 0, Load_int32(fromValue, 0)); + Store_int32(typedObj, offset + 4, Load_int32(fromValue, 4)); + Store_int32(typedObj, offset + 8, Load_int32(fromValue, 8)); + Store_int32(typedObj, offset + 12, Load_int32(fromValue, 12)); + break; + case JS_SIMDTYPEREPR_UINT8X16: + Store_uint8(typedObj, offset + 0, Load_uint8(fromValue, 0)); + Store_uint8(typedObj, offset + 1, Load_uint8(fromValue, 1)); + Store_uint8(typedObj, offset + 2, Load_uint8(fromValue, 2)); + Store_uint8(typedObj, offset + 3, Load_uint8(fromValue, 3)); + Store_uint8(typedObj, offset + 4, Load_uint8(fromValue, 4)); + Store_uint8(typedObj, offset + 5, Load_uint8(fromValue, 5)); + Store_uint8(typedObj, offset + 6, Load_uint8(fromValue, 6)); + Store_uint8(typedObj, offset + 7, Load_uint8(fromValue, 7)); + Store_uint8(typedObj, offset + 8, Load_uint8(fromValue, 8)); + Store_uint8(typedObj, offset + 9, Load_uint8(fromValue, 9)); + Store_uint8(typedObj, offset + 10, Load_uint8(fromValue, 10)); + Store_uint8(typedObj, offset + 11, Load_uint8(fromValue, 11)); + Store_uint8(typedObj, offset + 12, Load_uint8(fromValue, 12)); + Store_uint8(typedObj, offset + 13, Load_uint8(fromValue, 13)); + Store_uint8(typedObj, offset + 14, Load_uint8(fromValue, 14)); + Store_uint8(typedObj, offset + 15, Load_uint8(fromValue, 15)); + break; + case JS_SIMDTYPEREPR_UINT16X8: + Store_uint16(typedObj, offset + 0, Load_uint16(fromValue, 0)); + Store_uint16(typedObj, offset + 2, Load_uint16(fromValue, 2)); + Store_uint16(typedObj, offset + 4, Load_uint16(fromValue, 4)); + Store_uint16(typedObj, offset + 6, Load_uint16(fromValue, 6)); + Store_uint16(typedObj, offset + 8, Load_uint16(fromValue, 8)); + Store_uint16(typedObj, offset + 10, Load_uint16(fromValue, 10)); + Store_uint16(typedObj, offset + 12, Load_uint16(fromValue, 12)); + Store_uint16(typedObj, offset + 14, Load_uint16(fromValue, 14)); + break; + case JS_SIMDTYPEREPR_UINT32X4: + Store_uint32(typedObj, offset + 0, Load_uint32(fromValue, 0)); + Store_uint32(typedObj, offset + 4, Load_uint32(fromValue, 4)); + Store_uint32(typedObj, offset + 8, Load_uint32(fromValue, 8)); + Store_uint32(typedObj, offset + 12, Load_uint32(fromValue, 12)); + break; + default: + assert(false, "Unhandled Simd type: " + type); + } +} + /////////////////////////////////////////////////////////////////////////// // C++ Wrappers // @@ -384,6 +629,241 @@ function TypedObjectArrayRedimension(newArrayType) { return NewDerivedTypedObject(newArrayType, this, 0); } +/////////////////////////////////////////////////////////////////////////// +// SIMD + +function SimdProtoString(type) { + switch (type) { + case JS_SIMDTYPEREPR_INT8X16: + return "Int8x16"; + case JS_SIMDTYPEREPR_INT16X8: + return "Int16x8"; + case JS_SIMDTYPEREPR_INT32X4: + return "Int32x4"; + case JS_SIMDTYPEREPR_UINT8X16: + return "Uint8x16"; + case JS_SIMDTYPEREPR_UINT16X8: + return "Uint16x8"; + case JS_SIMDTYPEREPR_UINT32X4: + return "Uint32x4"; + case JS_SIMDTYPEREPR_FLOAT32X4: + return "Float32x4"; + case JS_SIMDTYPEREPR_FLOAT64X2: + return "Float64x2"; + case JS_SIMDTYPEREPR_BOOL8X16: + return "Bool8x16"; + case JS_SIMDTYPEREPR_BOOL16X8: + return "Bool16x8"; + case JS_SIMDTYPEREPR_BOOL32X4: + return "Bool32x4"; + case JS_SIMDTYPEREPR_BOOL64X2: + return "Bool64x2"; + } + + assert(false, "Unhandled type constant"); + return undefined; +} + +function SimdTypeToLength(type) { + switch (type) { + case JS_SIMDTYPEREPR_INT8X16: + case JS_SIMDTYPEREPR_BOOL8X16: + return 16; + case JS_SIMDTYPEREPR_INT16X8: + case JS_SIMDTYPEREPR_BOOL16X8: + return 8; + case JS_SIMDTYPEREPR_INT32X4: + case JS_SIMDTYPEREPR_FLOAT32X4: + case JS_SIMDTYPEREPR_BOOL32X4: + return 4; + case JS_SIMDTYPEREPR_FLOAT64X2: + case JS_SIMDTYPEREPR_BOOL64X2: + return 2; + } + + assert(false, "Unhandled type constant"); + return undefined; +} + +// This implements SIMD.*.prototype.valueOf(). +// Once we have proper value semantics for SIMD types, this function should just +// perform a type check and return this. +// For now, throw a TypeError unconditionally since valueOf() was probably +// called from ToNumber() which is supposed to throw when attempting to convert +// a SIMD value to a number. +function SimdValueOf() { + if (!IsObject(this) || !ObjectIsTypedObject(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD", "valueOf", typeof this); + + var descr = TypedObjectTypeDescr(this); + + if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD", "valueOf", typeof this); + + ThrowTypeError(JSMSG_SIMD_TO_NUMBER); +} + +function SimdToSource() { + if (!IsObject(this) || !ObjectIsTypedObject(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toSource", typeof this); + + var descr = TypedObjectTypeDescr(this); + + if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toSource", typeof this); + + return SimdFormatString(descr, this); +} + +function SimdToString() { + if (!IsObject(this) || !ObjectIsTypedObject(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toString", typeof this); + + var descr = TypedObjectTypeDescr(this); + + if (DESCR_KIND(descr) != JS_TYPEREPR_SIMD_KIND) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "SIMD.*", "toString", typeof this); + + return SimdFormatString(descr, this); +} + +function SimdFormatString(descr, typedObj) { + var typerepr = DESCR_TYPE(descr); + var protoString = SimdProtoString(typerepr); + switch (typerepr) { + case JS_SIMDTYPEREPR_INT8X16: { + var s1 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 7); + var s9 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 8); + var s10 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 9); + var s11 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 10); + var s12 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 11); + var s13 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 12); + var s14 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 13); + var s15 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 14); + var s16 = callFunction(std_SIMD_Int8x16_extractLane, null, typedObj, 15); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`; + } + case JS_SIMDTYPEREPR_INT16X8: { + var s1 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Int16x8_extractLane, null, typedObj, 7); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`; + } + case JS_SIMDTYPEREPR_INT32X4: { + var x = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 1); + var z = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 2); + var w = callFunction(std_SIMD_Int32x4_extractLane, null, typedObj, 3); + return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`; + } + case JS_SIMDTYPEREPR_UINT8X16: { + var s1 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 7); + var s9 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 8); + var s10 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 9); + var s11 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 10); + var s12 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 11); + var s13 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 12); + var s14 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 13); + var s15 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 14); + var s16 = callFunction(std_SIMD_Uint8x16_extractLane, null, typedObj, 15); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`; + } + case JS_SIMDTYPEREPR_UINT16X8: { + var s1 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Uint16x8_extractLane, null, typedObj, 7); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`; + } + case JS_SIMDTYPEREPR_UINT32X4: { + var x = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 1); + var z = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 2); + var w = callFunction(std_SIMD_Uint32x4_extractLane, null, typedObj, 3); + return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`; + } + case JS_SIMDTYPEREPR_FLOAT32X4: { + var x = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 1); + var z = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 2); + var w = callFunction(std_SIMD_Float32x4_extractLane, null, typedObj, 3); + return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`; + } + case JS_SIMDTYPEREPR_FLOAT64X2: { + var x = callFunction(std_SIMD_Float64x2_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Float64x2_extractLane, null, typedObj, 1); + return `SIMD.${protoString}(${x}, ${y})`; + } + case JS_SIMDTYPEREPR_BOOL8X16: { + var s1 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 7); + var s9 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 8); + var s10 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 9); + var s11 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 10); + var s12 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 11); + var s13 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 12); + var s14 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 13); + var s15 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 14); + var s16 = callFunction(std_SIMD_Bool8x16_extractLane, null, typedObj, 15); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8}, ${s9}, ${s10}, ${s11}, ${s12}, ${s13}, ${s14}, ${s15}, ${s16})`; + } + case JS_SIMDTYPEREPR_BOOL16X8: { + var s1 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 0); + var s2 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 1); + var s3 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 2); + var s4 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 3); + var s5 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 4); + var s6 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 5); + var s7 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 6); + var s8 = callFunction(std_SIMD_Bool16x8_extractLane, null, typedObj, 7); + return `SIMD.${protoString}(${s1}, ${s2}, ${s3}, ${s4}, ${s5}, ${s6}, ${s7}, ${s8})`; + } + case JS_SIMDTYPEREPR_BOOL32X4: { + var x = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 1); + var z = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 2); + var w = callFunction(std_SIMD_Bool32x4_extractLane, null, typedObj, 3); + return `SIMD.${protoString}(${x}, ${y}, ${z}, ${w})`; + } + case JS_SIMDTYPEREPR_BOOL64X2: { + var x = callFunction(std_SIMD_Bool64x2_extractLane, null, typedObj, 0); + var y = callFunction(std_SIMD_Bool64x2_extractLane, null, typedObj, 1); + return `SIMD.${protoString}(${x}, ${y})`; + } + } + assert(false, "unexpected SIMD kind"); + return "?"; +} + /////////////////////////////////////////////////////////////////////////// // Miscellaneous diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h index a19e2aff6493..41f30ab3e6fc 100644 --- a/js/src/builtin/TypedObjectConstants.h +++ b/js/src/builtin/TypedObjectConstants.h @@ -53,7 +53,7 @@ #define JS_DESCR_SLOT_ARRAYPROTO 6 // Lazily created prototype for arrays #define JS_DESCR_SLOT_TRACE_LIST 7 // List of references for use in tracing -// Slots on scalars, references +// Slots on scalars, references, and SIMD objects #define JS_DESCR_SLOT_TYPE 8 // Type code // Slots on array descriptors @@ -75,6 +75,7 @@ #define JS_TYPEREPR_REFERENCE_KIND 2 #define JS_TYPEREPR_STRUCT_KIND 3 #define JS_TYPEREPR_ARRAY_KIND 4 +#define JS_TYPEREPR_SIMD_KIND 5 // These constants are for use exclusively in JS code. In C++ code, // prefer Scalar::Int8 etc, which allows you to write a switch which will @@ -88,6 +89,10 @@ #define JS_SCALARTYPEREPR_FLOAT32 6 #define JS_SCALARTYPEREPR_FLOAT64 7 #define JS_SCALARTYPEREPR_UINT8_CLAMPED 8 +#define JS_SCALARTYPEREPR_FLOAT32X4 11 +#define JS_SCALARTYPEREPR_INT8X16 12 +#define JS_SCALARTYPEREPR_INT16X8 13 +#define JS_SCALARTYPEREPR_INT32X4 14 // These constants are for use exclusively in JS code. In C++ code, // prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows @@ -97,4 +102,20 @@ #define JS_REFERENCETYPEREPR_OBJECT 1 #define JS_REFERENCETYPEREPR_STRING 2 +// These constants are for use exclusively in JS code. In C++ code, prefer +// SimdType::Int32x4 etc, since that allows you to write a switch which will +// receive a warning if you omit a case. +#define JS_SIMDTYPEREPR_INT8X16 0 +#define JS_SIMDTYPEREPR_INT16X8 1 +#define JS_SIMDTYPEREPR_INT32X4 2 +#define JS_SIMDTYPEREPR_UINT8X16 3 +#define JS_SIMDTYPEREPR_UINT16X8 4 +#define JS_SIMDTYPEREPR_UINT32X4 5 +#define JS_SIMDTYPEREPR_FLOAT32X4 6 +#define JS_SIMDTYPEREPR_FLOAT64X2 7 +#define JS_SIMDTYPEREPR_BOOL8X16 8 +#define JS_SIMDTYPEREPR_BOOL16X8 9 +#define JS_SIMDTYPEREPR_BOOL32X4 10 +#define JS_SIMDTYPEREPR_BOOL64X2 11 + #endif diff --git a/js/src/devtools/automation/cgc-jittest-timeouts.txt b/js/src/devtools/automation/cgc-jittest-timeouts.txt index 1e7a068e794f..e323965d5368 100644 --- a/js/src/devtools/automation/cgc-jittest-timeouts.txt +++ b/js/src/devtools/automation/cgc-jittest-timeouts.txt @@ -1,3 +1,4 @@ +SIMD/nursery-overflow.js asm.js/testBug1117235.js asm.js/testParallelCompile.js auto-regress/bug653395.js diff --git a/js/src/doc/JITOptimizations/Outcomes.md b/js/src/doc/JITOptimizations/Outcomes.md index 62585d93ca46..abfb09791151 100644 --- a/js/src/doc/JITOptimizations/Outcomes.md +++ b/js/src/doc/JITOptimizations/Outcomes.md @@ -152,6 +152,11 @@ Failed to do range check of element access on a typed object. ### AccessNotDense +### AccessNotSimdObject + +The observed type of the target of the property access doesn't guarantee +that it is a SIMD object. + ### AccessNotTypedObject The observed type of the target of the property access doesn't guarantee @@ -217,6 +222,15 @@ the keys have never been observed to be a String, Symbol, or Int32. IonMonkey only generates inline caches for element accesses which are either on dense objects (e.g. dense Arrays), or Typed Arrays. +### NoSimdJitSupport + +Optimization failed because SIMD JIT support was not enabled. + +### SimdTypeNotOptimized + +The type observed as being retrieved from this property access did not +match an optimizable type. + ### HasCommonInliningPath Inlining was abandoned because the inlining call path was repeated. A diff --git a/js/src/jit-test/jit_test.py b/js/src/jit-test/jit_test.py index 64b9918fe0bb..9265c502d1e0 100755 --- a/js/src/jit-test/jit_test.py +++ b/js/src/jit-test/jit_test.py @@ -253,6 +253,9 @@ def main(argv): # This part is equivalent to: # skip-if = coverage if os.getenv('GCOV_PREFIX') is not None: + # GCOV errors. + options.exclude += [os.path.join('asm.js', 'testSIMD.js')] # Bug 1347245 + # JSVM errors. options.exclude += [os.path.join('basic', 'functionnames.js')] # Bug 1369783 options.exclude += [os.path.join('debug', 'Debugger-findScripts-23.js')] diff --git a/js/src/jit-test/lib/simd.js b/js/src/jit-test/lib/simd.js new file mode 100644 index 000000000000..f275c6f08107 --- /dev/null +++ b/js/src/jit-test/lib/simd.js @@ -0,0 +1,109 @@ +if (!this.hasOwnProperty("SIMD")) + quit(); + +function booleanBinaryX4(op, v, w) { + var arr = []; + var [varr, warr] = [simdToArray(v), simdToArray(w)]; + for (var i = 0; i < 4; i++) + arr[i] = op(varr[i], warr[i]); + return arr; +} + +function binaryX(op, v, w) { + var arr = []; + var [varr, warr] = [simdToArray(v), simdToArray(w)]; + [varr, warr] = [varr.map(Math.fround), warr.map(Math.fround)]; + for (var i = 0; i < varr.length; i++) + arr[i] = op(varr[i], warr[i]); + return arr.map(Math.fround); +} + +function unaryX4(op, v, coerceFunc) { + var arr = []; + var varr = simdToArray(v).map(coerceFunc); + for (var i = 0; i < 4; i++) + arr[i] = op(varr[i]); + return arr.map(coerceFunc); +} + +function assertNear(a, b) { + assertEq((a != a && b != b) || Math.abs(a - b) < 0.001, true); +} + +function GetType(v) { + var pt = Object.getPrototypeOf(v); + switch (pt) { + case SIMD.Int8x16.prototype: return SIMD.Int8x16; + case SIMD.Int16x8.prototype: return SIMD.Int16x8; + case SIMD.Int32x4.prototype: return SIMD.Int32x4; + case SIMD.Uint8x16.prototype: return SIMD.Uint8x16; + case SIMD.Uint16x8.prototype: return SIMD.Uint16x8; + case SIMD.Uint32x4.prototype: return SIMD.Uint32x4; + case SIMD.Float32x4.prototype: return SIMD.Float32x4; + case SIMD.Bool8x16.prototype: return SIMD.Bool8x16; + case SIMD.Bool16x8.prototype: return SIMD.Bool16x8; + case SIMD.Bool32x4.prototype: return SIMD.Bool32x4; + } + throw "unexpected SIMD type"; +} + +function GetLength(t) { + switch (t) { + case SIMD.Int8x16: return 16; + case SIMD.Int16x8: return 8; + case SIMD.Int32x4: return 4; + case SIMD.Uint8x16: return 16; + case SIMD.Uint16x8: return 8; + case SIMD.Uint32x4: return 4; + case SIMD.Float32x4: return 4; + case SIMD.Bool8x16: return 16; + case SIMD.Bool16x8: return 8; + case SIMD.Bool32x4: return 4; + } + throw "unexpected SIMD type"; +} + +function assertEqVec(v, w) { + var typeV = GetType(v); + var lengthV = GetLength(typeV); + var ext = typeV.extractLane; + assertEq(GetType(w), typeV); + for (var i = 0; i < lengthV; i++) + assertEq(ext(v, i), ext(w, i)); +} + +function assertEqVecArr(v, w) { + var typeV = GetType(v); + var lengthV = GetLength(typeV); + var ext = typeV.extractLane; + assertEq(w.length, lengthV); + + for (var i = 0; i < lengthV; i++) + assertEq(ext(v, i), w[i]); +} + +function assertEqX4(vec, arr, ...opts) { + + var assertFunc; + if (opts.length == 1 && typeof opts[0] !== 'undefined') { + assertFunc = opts[0]; + } else { + assertFunc = assertEq; + } + + var Type = GetType(vec); + + assertFunc(Type.extractLane(vec, 0), arr[0]); + assertFunc(Type.extractLane(vec, 1), arr[1]); + assertFunc(Type.extractLane(vec, 2), arr[2]); + assertFunc(Type.extractLane(vec, 3), arr[3]); +} + +function simdToArray(vec) { + var Type = GetType(vec); + var Length = GetLength(Type); + var a = []; + for (var i = 0; i < Length; i++) + a.push(Type.extractLane(vec, i)); + return a; +} diff --git a/js/src/jit-test/tests/SIMD/anyall.js b/js/src/jit-test/tests/SIMD/anyall.js new file mode 100644 index 000000000000..3f6176d556d0 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/anyall.js @@ -0,0 +1,38 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function all(B, n) { + var a = B.splat(true); + for (var i = 0; i < n; i++) { + var b = B.replaceLane(a, i, false); + assertEq(B.allTrue(b), false); + var c = B.replaceLane(b, i, true); + assertEq(B.allTrue(c), true); + } +} + +function any(B, n) { + var a = B.splat(false); + for (var i = 0; i < n; i++) { + var b = B.replaceLane(a, i, true); + assertEq(B.anyTrue(b), true); + var c = B.replaceLane(b, i, false); + assertEq(B.anyTrue(c), false); + } +} + +function f() { + for (var j = 0; j < 200; j++) { + all(SIMD.Bool64x2, 2) + any(SIMD.Bool64x2, 2) + all(SIMD.Bool32x4, 4) + any(SIMD.Bool32x4, 4) + all(SIMD.Bool16x8, 8) + any(SIMD.Bool16x8, 8) + all(SIMD.Bool8x16, 16) + any(SIMD.Bool8x16, 16) + } +} + +f() diff --git a/js/src/jit-test/tests/SIMD/binary-arith.js b/js/src/jit-test/tests/SIMD/binary-arith.js new file mode 100644 index 000000000000..74211d46db91 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/binary-arith.js @@ -0,0 +1,30 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var i1 = SIMD.Int32x4(1, 2, 3, 4); + var i2 = SIMD.Int32x4(4, 3, 2, 1); + + var f1 = SIMD.Float32x4(1, 2, 3, 4); + var f2 = SIMD.Float32x4(4, 3, 2, 1); + + var i8_1 = SIMD.Int8x16(1, 2, 3, 4, 20, 30, 40, 50, 100, 115, 120, 125); + var i8_2 = SIMD.Int8x16(4, 3, 2, 1, 8, 7, 6, 5, 12, 11, 10, 9); + + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Float32x4.add(f1, f2), binaryX((x, y) => x + y, f1, f2)); + assertEqX4(SIMD.Float32x4.sub(f1, f2), binaryX((x, y) => x - y, f1, f2)); + assertEqX4(SIMD.Float32x4.mul(f1, f2), binaryX((x, y) => x * y, f1, f2)); + + assertEqX4(SIMD.Int32x4.add(i1, i2), binaryX((x, y) => x + y, i1, i2)); + assertEqX4(SIMD.Int32x4.sub(i1, i2), binaryX((x, y) => x - y, i1, i2)); + assertEqX4(SIMD.Int32x4.mul(i1, i2), binaryX((x, y) => x * y, i1, i2)); + + assertEqX4(SIMD.Int8x16.add(i8_1, i8_2), binaryX((x, y) => (x + y) << 24 >> 24, i8_1, i8_2)); + assertEqX4(SIMD.Int8x16.sub(i8_1, i8_2), binaryX((x, y) => (x - y) << 24 >> 24, i8_1, i8_2)); + assertEqX4(SIMD.Int8x16.mul(i8_1, i8_2), binaryX((x, y) => (x * y) << 24 >> 24, i8_1, i8_2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/bool32x4-arith.js b/js/src/jit-test/tests/SIMD/bool32x4-arith.js new file mode 100644 index 000000000000..bafceefa9e6c --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bool32x4-arith.js @@ -0,0 +1,15 @@ +load(libdir + "simd.js"); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var b1 = SIMD.Bool32x4(true, false, true, false); + var b2 = SIMD.Bool32x4(true, true, true, true); + do { + assertEqX4(SIMD.Bool32x4.and(b1, b2), booleanBinaryX4((x, y) => x && y, b1, b2)); + assertEqX4(SIMD.Bool32x4.or(b1, b2), booleanBinaryX4((x, y) => x || y, b1, b2)); + assertEqX4(SIMD.Bool32x4.xor(b1, b2), booleanBinaryX4((x, y) => x != y, b1, b2)); + } while (!inIon()); +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/bool32x4-const.js b/js/src/jit-test/tests/SIMD/bool32x4-const.js new file mode 100644 index 000000000000..01c83d9354a7 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bool32x4-const.js @@ -0,0 +1,65 @@ +load(libdir + "simd.js"); + +setJitCompilerOption("ion.warmup.trigger", 50); + +// Test constant folding into the Bool32x4 constructor. +// Verify that we get the truthiness right, c.f. the ECMA ToBoolean() function. +function f1() { + var B = SIMD.Bool32x4; + var S = SIMD.Bool32x4.splat; + return [ + B(false, false, false, true), + B(true), + B(undefined, null, "", "x"), + B({}, 0, 1, -0.0), + B(NaN, -NaN, Symbol(), createIsHTMLDDA()), + + S(false), + S(true), + S(undefined), + S(null), + + S(""), + S("x"), + S(0), + S(1), + + S({}), + S(-0.0), + S(NaN), + S(Symbol()), + + S(createIsHTMLDDA()) + ]; +} + +function f() { + for (var i = 0; i < 100; i++) { + var a = f1() + assertEqX4(a[0], [false, false, false, true]); + assertEqX4(a[1], [true, false, false, false]); + assertEqX4(a[2], [false, false, false, true]); + assertEqX4(a[3], [true, false, true, false]); + assertEqX4(a[4], [false, false, true, false]); + + // Splats. + assertEqX4(a[5], [false, false, false, false]); + assertEqX4(a[6], [true, true, true, true]); + assertEqX4(a[7], [false, false, false, false]); + assertEqX4(a[8], [false, false, false, false]); + + assertEqX4(a[9], [false, false, false, false]); + assertEqX4(a[10], [true, true, true, true]); + assertEqX4(a[11], [false, false, false, false]); + assertEqX4(a[12], [true, true, true, true]); + + assertEqX4(a[13], [true, true, true, true]); + assertEqX4(a[14], [false, false, false, false]); + assertEqX4(a[15], [false, false, false, false]); + assertEqX4(a[16], [true, true, true, true]); + + assertEqX4(a[17], [false, false, false, false]); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/bug1109911.js b/js/src/jit-test/tests/SIMD/bug1109911.js new file mode 100644 index 000000000000..23a5c5721d80 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1109911.js @@ -0,0 +1,11 @@ +if (typeof TypedObject === "undefined" || typeof SIMD === 'undefined') + quit(); + +var Int32x4 = SIMD.Int32x4; +var a = Int32x4((4294967295), 200, 300, 400); +addCase( new Array(Math.pow(2,12)) ); +for ( var arg = "", i = 0; i < Math.pow(2,12); i++ ) {} +addCase( a ); +function addCase(object) { + object.length +} diff --git a/js/src/jit-test/tests/SIMD/bug1121299.js b/js/src/jit-test/tests/SIMD/bug1121299.js new file mode 100644 index 000000000000..17ca46e2ec09 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1121299.js @@ -0,0 +1,31 @@ +if (!this.hasOwnProperty("SIMD")) + quit(); + +setJitCompilerOption("baseline.warmup.trigger", 10); +setJitCompilerOption("ion.warmup.trigger", 30); + +function test_1(i) { + if (i >= 40) + return; + var a = SIMD.Float32x4(1.1, 2.2, 3.3, 4.6); + SIMD.Int32x4.fromFloat32x4(a); + test_1(i + 1); +} +test_1(0); + + +var Float32x4 = SIMD.Float32x4; +function test_2() { + var Array = Float32x4.array(3); + var array = new Array([ + Float32x4(1, 2, 3, 4), + Float32x4(5, 6, 7, 8), + Float32x4(9, 10, 11, 12) + ]); + if (typeof reportCompare === "function") + reportCompare(true, true); +} +test_2(); +evaluate("test_2(); test_2();", { + isRunOnce: true, +}); diff --git a/js/src/jit-test/tests/SIMD/bug1123631.js b/js/src/jit-test/tests/SIMD/bug1123631.js new file mode 100644 index 000000000000..28c0e0aa157f --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1123631.js @@ -0,0 +1,9 @@ +if (!this.hasOwnProperty("SIMD")) + quit(); + +var Float64x2 = SIMD.Float64x2; +function test() { + var a = Float64x2(1, 2); +} +test(); +test(); diff --git a/js/src/jit-test/tests/SIMD/bug1130845.js b/js/src/jit-test/tests/SIMD/bug1130845.js new file mode 100644 index 000000000000..2baf3865d21b --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1130845.js @@ -0,0 +1,15 @@ +if (!this.hasOwnProperty("SIMD")) + quit(); + +var Int32x4 = SIMD.Int32x4; +function test() { + var a = Int32x4(); + var b = Int32x4(10, 20, 30, 40); + var c = SIMD.Int32x4.and(a, b); + assertEq(Int32x4.extractLane(c, 0), 0); + return 0; +} +test(); +var u = [], v = []; +for (var j=0; j v, w); + function assertEqX4(...opts) {} +} +gczeal(1); +function f() { + SIMD.Float32x4(); + var i1 = SIMD.Int32x4(); + for (j = 0; j < 100000; ++j, eval.eval) + assertEqVec(SIMD.Int32x4.check(i1), i1); +} +f(); + diff --git a/js/src/jit-test/tests/SIMD/bug1273483.js b/js/src/jit-test/tests/SIMD/bug1273483.js new file mode 100644 index 000000000000..55fb09ace2d5 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1273483.js @@ -0,0 +1,9 @@ +if (typeof SIMD === 'undefined') + quit(); + +Int8x16 = SIMD.Int8x16; +var Int32x4 = SIMD.Int32x4; +function testSwizzleForType(type) { return type(); } +testSwizzleForType(Int8x16); +function testSwizzleInt32x4() { return testSwizzleForType(Int32x4); } +testSwizzleInt32x4(); diff --git a/js/src/jit-test/tests/SIMD/bug1296640-gc-args.js b/js/src/jit-test/tests/SIMD/bug1296640-gc-args.js new file mode 100644 index 000000000000..4dbe9541067a --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1296640-gc-args.js @@ -0,0 +1,9 @@ +if (typeof gczeal === 'undefined' || typeof SIMD === 'undefined') { + quit(); +} + +gczeal(9, 2); +var Int8x16 = SIMD.Int8x16; +var v = Int8x16(); +var good = { valueOf: () => 21 }; +Int8x16.shiftLeftByScalar(v, good); diff --git a/js/src/jit-test/tests/SIMD/bug1303780-gc-args.js b/js/src/jit-test/tests/SIMD/bug1303780-gc-args.js new file mode 100644 index 000000000000..a894d532ef02 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1303780-gc-args.js @@ -0,0 +1,12 @@ +if (typeof gczeal === 'undefined' || typeof SIMD === 'undefined') { + quit(); +} + +gczeal(14,2); +var Float32x4 = SIMD.Float32x4; +function test() { + var v = Float32x4(1,2,3,4); + var good = {valueOf: () => 42}; + Float32x4.replaceLane(v, 0, good); +} +test(); diff --git a/js/src/jit-test/tests/SIMD/bug1435317.js b/js/src/jit-test/tests/SIMD/bug1435317.js new file mode 100644 index 000000000000..fe4463426cc7 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug1435317.js @@ -0,0 +1,25 @@ +load(libdir + 'simd.js'); + +var ab = new ArrayBuffer(64 * 1024); +var arr = new Uint8Array(ab); + +(function(glob, imp, b) { + "use asm"; + var arr = new glob.Uint8Array(b); + return {} +})(this, null, ab); + +function testSimdX4() { + for (var i = 10; i --> 0;) { + var caught; + try { + v = SIMD.Int32x4.load(arr, 65534); + } catch (e) { + caught = e; + } + assertEq(caught instanceof RangeError, true); + } +} + +setJitCompilerOption('ion.warmup.trigger', 0); +testSimdX4(); diff --git a/js/src/jit-test/tests/SIMD/bug953108.js b/js/src/jit-test/tests/SIMD/bug953108.js new file mode 100644 index 000000000000..a8ae80e17098 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/bug953108.js @@ -0,0 +1,10 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +if (!this.hasOwnProperty("TypedObject") || !this.hasOwnProperty("SIMD")) + quit(); + +var Float32x4 = SIMD.Float32x4; +Float32x4.array(1); diff --git a/js/src/jit-test/tests/SIMD/check.js b/js/src/jit-test/tests/SIMD/check.js new file mode 100644 index 000000000000..bef0b6c688a9 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/check.js @@ -0,0 +1,25 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var f1 = SIMD.Float32x4(1, 2, 3, 4); + var i1 = SIMD.Int32x4(1, 2, -3, 4); + var b1 = SIMD.Bool32x4(true, true, false, true); + var i = 0; + try { + for (; i < 150; i++) { + if (i > 148) + i1 = f1; + assertEqVec(SIMD.Int32x4.check(i1), i1); + assertEqVec(SIMD.Float32x4.check(f1), f1); + assertEqVec(SIMD.Bool32x4.check(b1), b1); + } + } catch (ex) { + assertEq(i, 149); + assertEq(ex instanceof TypeError, true); + } +} + +f(); + diff --git a/js/src/jit-test/tests/SIMD/compare.js b/js/src/jit-test/tests/SIMD/compare.js new file mode 100644 index 000000000000..21dca20cd8f1 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/compare.js @@ -0,0 +1,39 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var f1 = SIMD.Float32x4(1, 2, 3, 4); + var f2 = SIMD.Float32x4(NaN, Infinity, 3.14, -0); + + var i1 = SIMD.Int32x4(1, 2, -3, 4); + var i2 = SIMD.Int32x4(1, -2, 3, 0); + + var u1 = SIMD.Uint32x4(1, 2, -3, 4); + var u2 = SIMD.Uint32x4(1, -2, 3, 0x80000000); + + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Int32x4.lessThan(i1, i2), [false, false, true, false]); + assertEqX4(SIMD.Int32x4.lessThanOrEqual(i1, i2), [true, false, true, false]); + assertEqX4(SIMD.Int32x4.equal(i1, i2), [true, false, false, false]); + assertEqX4(SIMD.Int32x4.notEqual(i1, i2), [false, true, true, true]); + assertEqX4(SIMD.Int32x4.greaterThan(i1, i2), [false, true, false, true]); + assertEqX4(SIMD.Int32x4.greaterThanOrEqual(i1, i2), [true, true, false, true]); + + assertEqX4(SIMD.Uint32x4.lessThan(u1, u2), [false, true, false, true]); + assertEqX4(SIMD.Uint32x4.lessThanOrEqual(u1, u2), [true, true, false, true]); + assertEqX4(SIMD.Uint32x4.equal(u1, u2), [true, false, false, false]); + assertEqX4(SIMD.Uint32x4.notEqual(u1, u2), [false, true, true, true]); + assertEqX4(SIMD.Uint32x4.greaterThan(u1, u2), [false, false, true, false]); + assertEqX4(SIMD.Uint32x4.greaterThanOrEqual(u1, u2), [true, false, true, false]); + + assertEqX4(SIMD.Float32x4.lessThan(f1, f2), [false, true, true, false]); + assertEqX4(SIMD.Float32x4.lessThanOrEqual(f1, f2), [false, true, true, false]); + assertEqX4(SIMD.Float32x4.equal(f1, f2), [false, false, false, false]); + assertEqX4(SIMD.Float32x4.notEqual(f1, f2), [true, true, true, true]); + assertEqX4(SIMD.Float32x4.greaterThan(f1, f2), [false, false, false, true]); + assertEqX4(SIMD.Float32x4.greaterThanOrEqual(f1, f2), [false, false, false, true]); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/complex-4.js b/js/src/jit-test/tests/SIMD/complex-4.js new file mode 100644 index 000000000000..ca5e8b0f67b4 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/complex-4.js @@ -0,0 +1,70 @@ +load(libdir + 'simd.js'); + +if (typeof SIMD === "undefined") + quit(); + +setJitCompilerOption("baseline.warmup.trigger", 10); +setJitCompilerOption("ion.warmup.trigger", 90); +var max = 100; // Make have the warm-up counter high enough to + // consider inlining functions. + +var f4 = SIMD.Int32x4; // :TODO: Support Float32x4 arith. +var f4add = f4.add; +var f4sub = f4.sub; +var f4mul = f4.mul; + +function c4mul(z1, z2) { + var { re: re1, im: im1 } = z1; + var { re: re2, im: im2 } = z2; + var rere = f4mul(re1, re2); + var reim = f4mul(re1, im2); + var imre = f4mul(im1, re2); + var imim = f4mul(im1, im2); + return { re: f4sub(rere, imim), im: f4add(reim, imre) }; +} + +function c4inv(z) { + var { re: re, im: im } = z; + var minus = f4(-1, -1, -1, -1); + return { re: re, im: f4mul(im, minus) }; +} + +function c4inv_inplace(z) { + var res = c4inv(z); + z.re = res.re; + z.im = res.im; +} + +function c4norm(z) { + var { re: re, im: im } = c4mul(z, c4inv(z)); + return re; +} + +function c4scale(z, s) { + var { re: re, im: im } = z; + var f4s = f4(s, s, s, s); + return { re: f4mul(re, f4s), im: f4mul(im, f4s) }; +} + +var rotate90 = { re: f4(0, 0, 0, 0), im: f4(1, 1, 1, 1) }; +var cardinals = { re: f4(1, 0, -1, 0), im: f4(0, 1, 0, -1) }; + +function test(dots) { + for (var j = 0; j < 4; j++) { + dots = c4mul(rotate90, dots); + if (j % 2 == 0) // Magic ! + c4inv_inplace(dots); + dots = c4scale(dots, 2); + } + return dots; +} + +assertEqX4(c4norm(cardinals), simdToArray(f4.splat(1))); +var cardinals16 = c4scale(cardinals, 16); + +for (var i = 0; i < max; i++) { + var res = test(cardinals); + assertEqX4(c4norm(res), simdToArray(f4.splat(16 * 16))); + assertEqX4(res.re, simdToArray(cardinals16.re)); + assertEqX4(res.im, simdToArray(cardinals16.im)); +} diff --git a/js/src/jit-test/tests/SIMD/convert.js b/js/src/jit-test/tests/SIMD/convert.js new file mode 100644 index 000000000000..b27a041e7f77 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/convert.js @@ -0,0 +1,68 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 30); + +var cast = (function() { + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + return { + fromInt32Bits(x) { + i32[0] = x; + return f32[0]; + }, + + fromFloat32Bits(x) { + f32[0] = x; + return i32[0]; + } + } +})(); + +function f() { + // No bailout here. + var f4 = SIMD.Float32x4(1, 2, 3, 4); + var i4 = SIMD.Int32x4(1, 2, 3, 4); + var BitOrZero = (x) => x | 0; + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Float32x4.fromInt32x4(i4), unaryX4(BitOrZero, f4, Math.fround)); + assertEqX4(SIMD.Float32x4.fromInt32x4Bits(i4), unaryX4(cast.fromInt32Bits, f4, Math.fround)); + assertEqX4(SIMD.Int32x4.fromFloat32x4(f4), unaryX4(Math.fround, i4, BitOrZero)); + assertEqX4(SIMD.Int32x4.fromFloat32x4Bits(f4), unaryX4(cast.fromFloat32Bits, i4, BitOrZero)); + } +} + +function uglyDuckling(val) { + // We bail out when i == 149 because the conversion will return + // 0x80000000 and the input actually wasn't in bounds. + val = Math.fround(val); + for (var i = 0; i < 150; i++) { + var caught = false; + try { + var v = SIMD.Float32x4(i < 149 ? 0 : val, 0, 0, 0) + SIMD.Int32x4.fromFloat32x4(v); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } +} + +function dontBail() { + // On x86, the conversion will return 0x80000000, which will imply that we + // check the input values. However, we shouldn't bail out in this case. + for (var i = 0; i < 150; i++) { + var v = SIMD.Float32x4(i < 149 ? 0 : -Math.pow(2, 31), 0, 0, 0) + SIMD.Int32x4.fromFloat32x4(v); + } +} + +f(); + +dontBail(); +dontBail(); + +uglyDuckling(Math.pow(2, 31)); +uglyDuckling(NaN); +uglyDuckling(-Math.pow(2, 32)); diff --git a/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js b/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js new file mode 100644 index 000000000000..63e9215d9fef --- /dev/null +++ b/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js @@ -0,0 +1,33 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function maxNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.max(x, y); +} + +function minNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.min(x, y); +} + +function f() { + var f1 = SIMD.Float32x4(1, 2, 3, 4); + var f2 = SIMD.Float32x4(4, 3, 2, 1); + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Float32x4.div(f1, f2), binaryX((x, y) => x / y, f1, f2)); + assertEqX4(SIMD.Float32x4.min(f1, f2), binaryX(Math.min, f1, f2)); + assertEqX4(SIMD.Float32x4.max(f1, f2), binaryX(Math.max, f1, f2)); + assertEqX4(SIMD.Float32x4.minNum(f1, f2), binaryX(minNum, f1, f2)); + assertEqX4(SIMD.Float32x4.maxNum(f1, f2), binaryX(maxNum, f1, f2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/getters.js b/js/src/jit-test/tests/SIMD/getters.js new file mode 100644 index 000000000000..5a895bbe2f10 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/getters.js @@ -0,0 +1,48 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var i4 = SIMD.Int32x4(1, -2, 3, -4); + var u4 = SIMD.Uint32x4(1, -2, 3, 0x88000000); + var b4 = SIMD.Bool32x4(true, true, false, true); + + + var bt4 = SIMD.Bool32x4(true, true, true, true); + var bf4 = SIMD.Bool32x4(false, false, false, false); + + var v = Math.fround(13.37); + var f4 = SIMD.Float32x4(13.37, NaN, Infinity, -0); + + for (var i = 0; i < 150; i++) { + assertEq(SIMD.Int32x4.extractLane(i4, 0), 1); + assertEq(SIMD.Int32x4.extractLane(i4, 1), -2); + assertEq(SIMD.Int32x4.extractLane(i4, 2), 3); + assertEq(SIMD.Int32x4.extractLane(i4, 3), -4); + + assertEq(SIMD.Uint32x4.extractLane(u4, 0), 1); + assertEq(SIMD.Uint32x4.extractLane(u4, 1), -2 >>> 0); + assertEq(SIMD.Uint32x4.extractLane(u4, 2), 3); + assertEq(SIMD.Uint32x4.extractLane(u4, 3), 0x88000000); + + assertEq(SIMD.Float32x4.extractLane(f4, 0), v); + assertEq(SIMD.Float32x4.extractLane(f4, 1), NaN); + assertEq(SIMD.Float32x4.extractLane(f4, 2), Infinity); + assertEq(SIMD.Float32x4.extractLane(f4, 3), -0); + + assertEq(SIMD.Bool32x4.extractLane(b4, 0), true); + assertEq(SIMD.Bool32x4.extractLane(b4, 1), true); + assertEq(SIMD.Bool32x4.extractLane(b4, 2), false); + assertEq(SIMD.Bool32x4.extractLane(b4, 3), true); + + assertEq(SIMD.Bool32x4.anyTrue(b4), true); + assertEq(SIMD.Bool32x4.allTrue(b4), false); + + assertEq(SIMD.Bool32x4.anyTrue(bt4), true); + assertEq(SIMD.Bool32x4.allTrue(bt4), true); + assertEq(SIMD.Bool32x4.anyTrue(bf4), false); + assertEq(SIMD.Bool32x4.allTrue(bf4), false); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/inline-missing-arguments.js b/js/src/jit-test/tests/SIMD/inline-missing-arguments.js new file mode 100644 index 000000000000..5ef91d072a38 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/inline-missing-arguments.js @@ -0,0 +1,81 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function test(i) { + assertEqX4(SIMD.Int32x4(), [0, 0, 0, 0]); + assertEqX4(SIMD.Int32x4(i), [i, 0, 0, 0]); + assertEqX4(SIMD.Int32x4(i, 1), [i, 1, 0, 0]); + assertEqX4(SIMD.Int32x4(i, 1, 2), [i, 1, 2, 0]); + assertEqX4(SIMD.Int32x4(i, 1, 2, 3), [i, 1, 2, 3]); + assertEqX4(SIMD.Int32x4(i, 1, 2, 3, 4), [i, 1, 2, 3]); + + assertEqVecArr(SIMD.Int16x8(), [0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i), [i, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i, 1), [i, 1, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i, 1, 2), [i, 1, 2, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i, 1, 2, 3), [i, 1, 2, 3, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i, 1, 2, 3, 4), [i, 1, 2, 3, 4, 0, 0, 0]); + assertEqVecArr(SIMD.Int16x8(i, 1, 2, 3, 4, 5, 6), + [i, 1, 2, 3, 4, 5, 6, 0]); + j = i & 32 + assertEqVecArr(SIMD.Int8x16(), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j), [j, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1), [j, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1, 2), [j, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1, 2, 3), [j, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1, 2, 3, 4), [j, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1, 2, 3, 4, 5, 6), + [j, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertEqVecArr(SIMD.Int8x16(j, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), + [j, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0]); + + assertEqX4(SIMD.Float32x4(), [NaN, NaN, NaN, NaN]); + assertEqX4(SIMD.Float32x4(i), [i, NaN, NaN, NaN]); + assertEqX4(SIMD.Float32x4(i, 1), [i, 1, NaN, NaN]); + assertEqX4(SIMD.Float32x4(i, 1, 2), [i, 1, 2, NaN]); + assertEqX4(SIMD.Float32x4(i, 1, 2, 3), [i, 1, 2, 3 ]); + assertEqX4(SIMD.Float32x4(i, 1, 2, 3, 4), [i, 1, 2, 3 ]); + + var b = i % 2 > 0 ; + assertEqX4(SIMD.Bool32x4(), [false, false, false, false]); + assertEqX4(SIMD.Bool32x4(b), [b, false, false, false]); + assertEqX4(SIMD.Bool32x4(b, true), [b, true, false, false]); + assertEqX4(SIMD.Bool32x4(b, false, true), [b, false, true, false]); + assertEqX4(SIMD.Bool32x4(b, false, true, true), [b, false, true, true ]); + assertEqX4(SIMD.Bool32x4(b, false, true, true, true), [b, false, true, true ]); + + assertEqVecArr(SIMD.Bool16x8(), + [false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b), + [b, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b, true), + [b, true, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b, false, true), + [b, false, true, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b, false, true, true), + [b, false, true, true, false, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b, false, true, true, true), + [b, false, true, true, true, false, false, false]); + assertEqVecArr(SIMD.Bool16x8(b, false, true, true, true, true), + [b, false, true, true, true, true, false, false]); + + assertEqVecArr(SIMD.Bool8x16(), + [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b), + [b, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b, true), + [b, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b, false, true), + [b, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b, false, true, true), + [b, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b, false, true, true, true), + [b, false, true, true, true, false, false, false, false, false, false, false, false, false, false, false]); + assertEqVecArr(SIMD.Bool8x16(b, false, true, true, true, true, false, true, true, true), + [b, false, true, true, true, true, false, true, true, true, false, false, false, false, false, false]); +} + +for(var i=0; i<300; i++) { + test(i); +} diff --git a/js/src/jit-test/tests/SIMD/load.js b/js/src/jit-test/tests/SIMD/load.js new file mode 100644 index 000000000000..5cdb8cce63d0 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/load.js @@ -0,0 +1,123 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 40); + +function f() { + var f32 = new Float32Array(16); + for (var i = 0; i < 16; i++) + f32[i] = i + 1; + + var f64 = new Float64Array(f32.buffer); + var i32 = new Int32Array(f32.buffer); + var u32 = new Uint32Array(f32.buffer); + var i16 = new Int16Array(f32.buffer); + var u16 = new Uint16Array(f32.buffer); + var i8 = new Int8Array(f32.buffer); + var u8 = new Uint8Array(f32.buffer); + + function testLoad() { + assertEqX4(SIMD.Float32x4.load(f64, 0), [1,2,3,4]); + assertEqX4(SIMD.Float32x4.load(f32, 1), [2,3,4,5]); + assertEqX4(SIMD.Float32x4.load(i32, 2), [3,4,5,6]); + assertEqX4(SIMD.Float32x4.load(i16, 3 << 1), [4,5,6,7]); + assertEqX4(SIMD.Float32x4.load(u16, 4 << 1), [5,6,7,8]); + assertEqX4(SIMD.Float32x4.load(i8 , 5 << 2), [6,7,8,9]); + assertEqX4(SIMD.Float32x4.load(u8 , 6 << 2), [7,8,9,10]); + + assertEqX4(SIMD.Float32x4.load(f64, (16 >> 1) - (4 >> 1)), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(f32, 16 - 4), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(i32, 16 - 4), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(i16, (16 << 1) - (4 << 1)), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(u16, (16 << 1) - (4 << 1)), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(i8, (16 << 2) - (4 << 2)), [13,14,15,16]); + assertEqX4(SIMD.Float32x4.load(u8, (16 << 2) - (4 << 2)), [13,14,15,16]); + } + + function testLoad1() { + assertEqX4(SIMD.Float32x4.load1(f64, 0), [1,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(f32, 1), [2,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i32, 2), [3,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i16, 3 << 1), [4,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(u16, 4 << 1), [5,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i8 , 5 << 2), [6,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(u8 , 6 << 2), [7,0,0,0]); + + assertEqX4(SIMD.Float32x4.load1(f64, (16 >> 1) - (4 >> 1)), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(f32, 16 - 4), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i32, 16 - 4), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i16, (16 << 1) - (4 << 1)), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(u16, (16 << 1) - (4 << 1)), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(i8, (16 << 2) - (4 << 2)), [13,0,0,0]); + assertEqX4(SIMD.Float32x4.load1(u8, (16 << 2) - (4 << 2)), [13,0,0,0]); + } + + function testLoad2() { + assertEqX4(SIMD.Float32x4.load2(f64, 0), [1,2,0,0]); + assertEqX4(SIMD.Float32x4.load2(f32, 1), [2,3,0,0]); + assertEqX4(SIMD.Float32x4.load2(i32, 2), [3,4,0,0]); + assertEqX4(SIMD.Float32x4.load2(i16, 3 << 1), [4,5,0,0]); + assertEqX4(SIMD.Float32x4.load2(u16, 4 << 1), [5,6,0,0]); + assertEqX4(SIMD.Float32x4.load2(i8 , 5 << 2), [6,7,0,0]); + assertEqX4(SIMD.Float32x4.load2(u8 , 6 << 2), [7,8,0,0]); + + assertEqX4(SIMD.Float32x4.load2(f64, (16 >> 1) - (4 >> 1)), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(f32, 16 - 4), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(i32, 16 - 4), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(i16, (16 << 1) - (4 << 1)), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(u16, (16 << 1) - (4 << 1)), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(i8, (16 << 2) - (4 << 2)), [13,14,0,0]); + assertEqX4(SIMD.Float32x4.load2(u8, (16 << 2) - (4 << 2)), [13,14,0,0]); + } + + function testLoad3() { + assertEqX4(SIMD.Float32x4.load3(f64, 0), [1,2,3,0]); + assertEqX4(SIMD.Float32x4.load3(f32, 1), [2,3,4,0]); + assertEqX4(SIMD.Float32x4.load3(i32, 2), [3,4,5,0]); + assertEqX4(SIMD.Float32x4.load3(i16, 3 << 1), [4,5,6,0]); + assertEqX4(SIMD.Float32x4.load3(u16, 4 << 1), [5,6,7,0]); + assertEqX4(SIMD.Float32x4.load3(i8 , 5 << 2), [6,7,8,0]); + assertEqX4(SIMD.Float32x4.load3(u8 , 6 << 2), [7,8,9,0]); + + assertEqX4(SIMD.Float32x4.load3(f64, (16 >> 1) - (4 >> 1)), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(f32, 16 - 4), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(i32, 16 - 4), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(i16, (16 << 1) - (4 << 1)), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(u16, (16 << 1) - (4 << 1)), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(i8, (16 << 2) - (4 << 2)), [13,14,15,0]); + assertEqX4(SIMD.Float32x4.load3(u8, (16 << 2) - (4 << 2)), [13,14,15,0]); + } + + for (var i = 0; i < 150; i++) { + testLoad(); + testLoad1(); + testLoad2(); + testLoad3(); + } +} + +f(); + +function testBailout(uglyDuckling) { + var f32 = new Float32Array(16); + for (var i = 0; i < 16; i++) + f32[i] = i + 1; + + var i8 = new Int8Array(f32.buffer); + + for (var i = 0; i < 150; i++) { + var caught = false; + try { + SIMD.Float32x4.load(i8, (i < 149) ? 0 : uglyDuckling); + } catch (e) { + print(e); + assertEq(e instanceof RangeError, true); + caught = true; + } + assertEq(i < 149 || caught, true); + } +} + +print('Testing range checks...'); +testBailout(-1); +testBailout(-15); +testBailout(12 * 4 + 1); diff --git a/js/src/jit-test/tests/SIMD/nursery-overflow.js b/js/src/jit-test/tests/SIMD/nursery-overflow.js new file mode 100644 index 000000000000..5aecff908bb4 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/nursery-overflow.js @@ -0,0 +1,29 @@ +load(libdir + 'simd.js'); + +if (typeof SIMD === "undefined") + quit(); + +setJitCompilerOption("baseline.warmup.trigger", 10); +setJitCompilerOption("ion.warmup.trigger", 30); + +var i4 = SIMD.Int32x4; +var i4sub = SIMD.Int32x4.sub; + +function simdbox(i) { + return i4(i, i, i, i); +} + +function test() { + var arr = []; + + // overflow the nursery with live SIMD objects. + for (var i = 0; i < 100000; i++) { + arr.push(simdbox(i)); + } + + return arr; +} + +var arr = test(); +for (var i = 0; i < arr.length; i++) + assertEqX4(arr[i], [i, i, i, i]); diff --git a/js/src/jit-test/tests/SIMD/recover.js b/js/src/jit-test/tests/SIMD/recover.js new file mode 100644 index 000000000000..a8fb0002ed4a --- /dev/null +++ b/js/src/jit-test/tests/SIMD/recover.js @@ -0,0 +1,70 @@ +load(libdir + 'simd.js'); + +if (!this.hasOwnProperty("SIMD")) + quit(); + +// This test case ensure that if we are able to optimize SIMD, then we can use +// recover instructions to get rid of the allocations. So, there is no value +// (and the test case would fail) if we are not able to inline SIMD +// constructors. +if (!isSimdAvailable()) + quit(); + +setJitCompilerOption("baseline.warmup.trigger", 10); +setJitCompilerOption("ion.warmup.trigger", 20); + +// This function is used to cause an invalidation after having removed a branch +// after DCE. This is made to check if we correctly recover an array +// allocation. +var uceFault = function (i) { + if (i > 98) + uceFault = function (i) { return true; }; + return false; +}; + +// Check that we can correctly recover a boxed value. +var uceFault_simdBox_i4 = eval(uneval(uceFault).replace('uceFault', 'uceFault_simdBox_i4')); +function simdBox_i4(i) { + var a = SIMD.Int32x4(i, i, i, i); + if (uceFault_simdBox_i4(i) || uceFault_simdBox_i4(i)) + assertEqX4(a, [i, i, i, i]); + assertRecoveredOnBailout(a, true); + return 0; +} + +var uceFault_simdBox_u4 = eval(uneval(uceFault).replace('uceFault', 'uceFault_simdBox_u4')); +function simdBox_u4(i) { + var a = SIMD.Uint32x4(i, 98 - i, i + 0x7ffffff0, i + 0xffffff00); + if (uceFault_simdBox_u4(i) || uceFault_simdBox_u4(i)) + assertEqX4(a, [i, 98 - i, i + 0x7ffffff0, i + 0xffffff00].map(x => x >>> 0)); + assertRecoveredOnBailout(a, true); + return 0; +} + +var uceFault_simdBox_f4 = eval(uneval(uceFault).replace('uceFault', 'uceFault_simdBox_f4')); +function simdBox_f4(i) { + var a = SIMD.Float32x4(i, i + 0.1, i + 0.2, i + 0.3); + if (uceFault_simdBox_f4(i) || uceFault_simdBox_f4(i)) + assertEqX4(a, [i, i + 0.1, i + 0.2, i + 0.3].map(Math.fround)); + assertRecoveredOnBailout(a, true); + return 0; +} + +var uceFault_simdBox_b4 = eval(uneval(uceFault).replace('uceFault', 'uceFault_simdBox_b4')); +function simdBox_b4(i) { + var val1 = i%2 === 0, + val2 = !val1; + + var a = SIMD.Bool32x4(val1, val2, val1, val2); + if (uceFault_simdBox_b4(i) || uceFault_simdBox_b4(i)) + assertEqX4(a, [val1, val2, val1, val2]); + assertRecoveredOnBailout(a, true); + return 0; +} + +for (var i = 0; i < 100; i++) { + simdBox_i4(i); + simdBox_u4(i); + simdBox_f4(i); + simdBox_b4(i); +} diff --git a/js/src/jit-test/tests/SIMD/replacelane.js b/js/src/jit-test/tests/SIMD/replacelane.js new file mode 100644 index 000000000000..c6b37ad5ab38 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/replacelane.js @@ -0,0 +1,181 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var f4 = SIMD.Float32x4(1, 2, 3, 4); + var i4 = SIMD.Int32x4(1, 2, 3, 4); + var b4 = SIMD.Bool32x4(true, false, true, false); + + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Int32x4.replaceLane(i4, 0, 42), [42, 2, 3, 4]); + assertEqX4(SIMD.Int32x4.replaceLane(i4, 1, 42), [1, 42, 3, 4]); + assertEqX4(SIMD.Int32x4.replaceLane(i4, 2, 42), [1, 2, 42, 4]); + assertEqX4(SIMD.Int32x4.replaceLane(i4, 3, 42), [1, 2, 3, 42]); + + assertEqX4(SIMD.Float32x4.replaceLane(f4, 0, 42), [42, 2, 3, 4]); + assertEqX4(SIMD.Float32x4.replaceLane(f4, 1, 42), [1, 42, 3, 4]); + assertEqX4(SIMD.Float32x4.replaceLane(f4, 2, 42), [1, 2, 42, 4]); + assertEqX4(SIMD.Float32x4.replaceLane(f4, 3, 42), [1, 2, 3, 42]); + + assertEqX4(SIMD.Bool32x4.replaceLane(b4, 0, false), [false, false, true, false]); + assertEqX4(SIMD.Bool32x4.replaceLane(b4, 1, true), [true, true, true, false]); + assertEqX4(SIMD.Bool32x4.replaceLane(b4, 2, false), [true, false, false, false]); + assertEqX4(SIMD.Bool32x4.replaceLane(b4, 3, true), [true, false, true, true]); + } +} + +f(); + +function e() { + var f4 = SIMD.Float32x4(1, 2, 3, 4); + var i4 = SIMD.Int32x4(1, 2, 3, 4); + var b4 = SIMD.Bool32x4(true, false, true, false); + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Int32x4.replaceLane(i < 149 ? i4 : f4, 0, 42); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Int32x4.replaceLane(i < 149 ? i4 : b4, 0, 42); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Int32x4.replaceLane(i4, i < 149 ? 0 : 4, 42); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Int32x4.replaceLane(i4, i < 149 ? 0 : 1.1, 42); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Float32x4.replaceLane(i < 149 ? f4 : i4, 0, 42); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Float32x4.replaceLane(i < 149 ? f4 : b4, 0, 42); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Float32x4.replaceLane(f4, i < 149 ? 0 : 4, 42); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Float32x4.replaceLane(f4, i < 149 ? 0 : 1.1, 42); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Bool32x4.replaceLane(i < 149 ? b4 : i4, 0, true); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Bool32x4.replaceLane(i < 149 ? b4 : f4, 0, true); + } catch(e) { + assertEq(e instanceof TypeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Bool32x4.replaceLane(b4, i < 149 ? 0 : 4, true); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + + for (let i = 0; i < 150; i++) { + let caught = false; + try { + let x = SIMD.Bool32x4.replaceLane(b4, i < 149 ? 0 : 1.1, true); + } catch(e) { + assertEq(e instanceof RangeError, true); + assertEq(i, 149); + caught = true; + } + assertEq(i < 149 || caught, true); + } + +} + +e(); diff --git a/js/src/jit-test/tests/SIMD/saturate.js b/js/src/jit-test/tests/SIMD/saturate.js new file mode 100644 index 000000000000..a98cf7ad799f --- /dev/null +++ b/js/src/jit-test/tests/SIMD/saturate.js @@ -0,0 +1,37 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +const INT8_MIN = -128; +const INT8_MAX = 127; +const UINT8_MAX = 255; + +function sat8(x) { + if (x < INT8_MIN) return INT8_MIN; + if (x > INT8_MAX) return INT8_MAX; + return x; +} + +function usat8(x) { + if (x < 0) return 0; + if (x > UINT8_MAX) return UINT8_MAX; + return x; +} + +function f() { + var i1 = SIMD.Int8x16(1, 100, 3, 4); + var i2 = SIMD.Int8x16(4, 30, 2, 1); + + var u1 = SIMD.Uint8x16(1, 2, 3, 4); + var u2 = SIMD.Uint8x16(4, 3, 2, 1); + + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Int8x16.addSaturate(i1, i2), binaryX((x, y) => sat8(x + y), i1, i2)); + assertEqX4(SIMD.Int8x16.subSaturate(i1, i2), binaryX((x, y) => sat8(x - y), i1, i2)); + + assertEqX4(SIMD.Uint8x16.addSaturate(u1, u2), binaryX((x, y) => usat8(x + y), u1, u2)); + assertEqX4(SIMD.Uint8x16.subSaturate(u1, u2), binaryX((x, y) => usat8(x - y), u1, u2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/select.js b/js/src/jit-test/tests/SIMD/select.js new file mode 100644 index 000000000000..3f0d783dac0d --- /dev/null +++ b/js/src/jit-test/tests/SIMD/select.js @@ -0,0 +1,35 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function select(type, mask, ifTrue, ifFalse) { + var arr = []; + for (var i = 0; i < 4; i++) { + var selector = SIMD.Bool32x4.extractLane(mask, i); + arr.push(type.extractLane(selector ? ifTrue : ifFalse, i)); + } + return arr; +} + +function f() { + var f1 = SIMD.Float32x4(1, 2, 3, 4); + var f2 = SIMD.Float32x4(NaN, Infinity, 3.14, -0); + + var i1 = SIMD.Int32x4(2, 3, 5, 8); + var i2 = SIMD.Int32x4(13, 37, 24, 42); + + var TTFT = SIMD.Bool32x4(true, true, false, true); + var TFTF = SIMD.Bool32x4(true, false, true, false); + + var mask = SIMD.Int32x4(0xdeadbeef, 0xbaadf00d, 0x00ff1ce, 0xdeadc0de); + + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Float32x4.select(TTFT, f1, f2), select(SIMD.Float32x4, TTFT, f1, f2)); + assertEqX4(SIMD.Float32x4.select(TFTF, f1, f2), select(SIMD.Float32x4, TFTF, f1, f2)); + + assertEqX4(SIMD.Int32x4.select(TFTF, i1, i2), select(SIMD.Int32x4, TFTF, i1, i2)); + assertEqX4(SIMD.Int32x4.select(TTFT, i1, i2), select(SIMD.Int32x4, TTFT, i1, i2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/shift.js b/js/src/jit-test/tests/SIMD/shift.js new file mode 100644 index 000000000000..8448e52ecbbf --- /dev/null +++ b/js/src/jit-test/tests/SIMD/shift.js @@ -0,0 +1,75 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function curry(f, arg) { return f.bind(null, arg); } + +function binaryLsh(count, v) { count &= 31; return (v << count) | 0; } +function lsh(count) { return curry(binaryLsh, count); } + +function binaryRsh(count, v) { count &= 31; return (v >> count) | 0; } +function rsh(count) { return curry(binaryRsh, count); } + +function binaryUlsh(count, v) { count &= 31; return (v << count) >>> 0; } +function ulsh(count) { return curry(binaryUlsh, count); } + +function binaryUrsh(count, v) { count &= 31; return v >>> count; } +function ursh(count) { return curry(binaryUrsh, count); } + +function f() { + var v = SIMD.Int32x4(1, 2, -3, 4); + var u = SIMD.Uint32x4(1, 0x55005500, -3, 0xaa00aa00); + var a = [1, 2, -3, 4]; + var b = [1, 0x55005500, -3, 0xaa00aa00]; + + var shifts = [-2, -1, 0, 1, 31, 32, 33]; + + var r; + for (var i = 0; i < 150; i++) { + // Constant shift counts + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, -1), a.map(lsh(-1))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 0), a.map(lsh(0))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 1), a.map(lsh(1))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 2), a.map(lsh(2))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 31), a.map(lsh(31))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 32), a.map(lsh(32))); + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, 33), a.map(lsh(33))); + + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, -1), a.map(rsh(31))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 0), a.map(rsh(0))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 1), a.map(rsh(1))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 2), a.map(rsh(2))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 31), a.map(rsh(31))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 32), a.map(rsh(32))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, 33), a.map(rsh(33))); + + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, -1), b.map(ulsh(-1))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 0), b.map(ulsh(0))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 1), b.map(ulsh(1))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 2), b.map(ulsh(2))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 31), b.map(ulsh(31))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 32), b.map(ulsh(32))); + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, 33), b.map(ulsh(33))); + + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, -1), b.map(ursh(-1))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 0), b.map(ursh(0))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 1), b.map(ursh(1))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 2), b.map(ursh(2))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 31), b.map(ursh(31))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 32), b.map(ursh(32))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, 33), b.map(ursh(33))); + + // Non constant shift counts + var c = shifts[i % shifts.length]; + + assertEqX4(SIMD.Int32x4.shiftLeftByScalar(v, c), a.map(lsh(c))); + assertEqX4(SIMD.Int32x4.shiftRightByScalar(v, c), a.map(rsh(c))); + + assertEqX4(SIMD.Uint32x4.shiftLeftByScalar(u, c), b.map(ulsh(c))); + assertEqX4(SIMD.Uint32x4.shiftRightByScalar(u, c), b.map(ursh(c))); + } + return r; +} + +f(); + diff --git a/js/src/jit-test/tests/SIMD/shuffle.js b/js/src/jit-test/tests/SIMD/shuffle.js new file mode 100644 index 000000000000..505f01131f5d --- /dev/null +++ b/js/src/jit-test/tests/SIMD/shuffle.js @@ -0,0 +1,86 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var i1 = SIMD.Int32x4(1, 2, 3, 4); + var i2 = SIMD.Int32x4(5, 6, 7, 8); + + var leet = Math.fround(13.37); + var f1 = SIMD.Float32x4(-.5, -0, Infinity, leet); + var f2 = SIMD.Float32x4(42, .5, 23, -10); + + // computes all rotations of a given array + function *gen(arr) { + var previous = arr.slice().splice(0, 4); + var i = 4; + for (var j = 0; j < 8; j++) { + yield previous.slice(); + previous = previous.splice(1, previous.length - 1); + previous.push(arr[i]); + i = (i + 1) % arr.length; + } + } + + var compI = []; + var baseI = []; + for (var i = 0; i < 8; i++) + baseI.push(SIMD.Int32x4.extractLane(i < 4 ? i1 : i2, i % 4)); + for (var k of gen(baseI)) + compI.push(k); + + var compF = []; + var baseF = []; + for (var i = 0; i < 8; i++) + baseF.push(SIMD.Float32x4.extractLane(i < 4 ? f1 : f2, i % 4)); + for (var k of gen(baseF)) + compF.push(k); + + for (var i = 0; i < 150; i++) { + // Variable lanes + var r = SIMD.Float32x4.shuffle(f1, f2, i % 8, (i + 1) % 8, (i + 2) % 8, (i + 3) % 8); + assertEqX4(r, compF[i % 8]); + + // Constant lanes + assertEqX4(SIMD.Float32x4.shuffle(f1, f2, 3, 2, 4, 5), [leet, Infinity, 42, .5]); + + // Variable lanes + var r = SIMD.Int32x4.shuffle(i1, i2, i % 8, (i + 1) % 8, (i + 2) % 8, (i + 3) % 8); + assertEqX4(r, compI[i % 8]); + + // Constant lanes + assertEqX4(SIMD.Int32x4.shuffle(i1, i2, 3, 2, 4, 5), [4, 3, 5, 6]); + } +} + +function testBailouts(expectException, uglyDuckling) { + var i1 = SIMD.Int32x4(1, 2, 3, 4); + var i2 = SIMD.Int32x4(5, 6, 7, 8); + + for (var i = 0; i < 150; i++) { + // Test bailouts + var value = i == 149 ? uglyDuckling : 0; + var caught = false; + try { + assertEqX4(SIMD.Int32x4.shuffle(i1, i2, value, 2, 4, 5), [1, 3, 5, 6]); + } catch(e) { + print(e); + caught = true; + assertEq(i, 149); + assertEq(e instanceof TypeError || e instanceof RangeError, true); + } + if (i == 149) + assertEq(caught, expectException); + } +} + +f(); +testBailouts(true, -1); +testBailouts(true, 8); +testBailouts(true, 2.5); +testBailouts(true, undefined); +testBailouts(true, {}); +testBailouts(true, 'one'); +testBailouts(false, false); +testBailouts(false, null); +testBailouts(false, " 0.0 "); diff --git a/js/src/jit-test/tests/SIMD/splat.js b/js/src/jit-test/tests/SIMD/splat.js new file mode 100644 index 000000000000..38eda3085eef --- /dev/null +++ b/js/src/jit-test/tests/SIMD/splat.js @@ -0,0 +1,15 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Int32x4.splat(42), [42, 42, 42, 42]); + assertEqX4(SIMD.Float32x4.splat(42), [42, 42, 42, 42]); + assertEqX4(SIMD.Bool32x4.splat(true), [true, true, true, true]); + assertEqX4(SIMD.Bool32x4.splat(false), [false, false, false, false]); + } +} + +f(); + diff --git a/js/src/jit-test/tests/SIMD/store.js b/js/src/jit-test/tests/SIMD/store.js new file mode 100644 index 000000000000..8cfa35427751 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/store.js @@ -0,0 +1,143 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 40); + +function f() { + var f32 = new Float32Array(16); + for (var i = 0; i < 16; i++) + f32[i] = i + 1; + + var f64 = new Float64Array(f32.buffer); + var i32 = new Int32Array(f32.buffer); + var u32 = new Uint32Array(f32.buffer); + var i16 = new Int16Array(f32.buffer); + var u16 = new Uint16Array(f32.buffer); + var i8 = new Int8Array(f32.buffer); + var u8 = new Uint8Array(f32.buffer); + + var f4 = SIMD.Float32x4(42, 43, 44, 45); + + function check(n) { + assertEq(f32[0], 42); + assertEq(f32[1], n > 1 ? 43 : 2); + assertEq(f32[2], n > 2 ? 44 : 3); + assertEq(f32[3], n > 3 ? 45 : 4); + + f32[0] = 1; + f32[1] = 2; + f32[2] = 3; + f32[3] = 4; + } + + function testStore() { + SIMD.Float32x4.store(f64, 0, f4); + check(4); + SIMD.Float32x4.store(f32, 0, f4); + check(4); + SIMD.Float32x4.store(i32, 0, f4); + check(4); + SIMD.Float32x4.store(u32, 0, f4); + check(4); + SIMD.Float32x4.store(i16, 0, f4); + check(4); + SIMD.Float32x4.store(u16, 0, f4); + check(4); + SIMD.Float32x4.store(i8, 0, f4); + check(4); + SIMD.Float32x4.store(u8, 0, f4); + check(4); + } + + function testStore1() { + SIMD.Float32x4.store1(f64, 0, f4); + check(1); + SIMD.Float32x4.store1(f32, 0, f4); + check(1); + SIMD.Float32x4.store1(i32, 0, f4); + check(1); + SIMD.Float32x4.store1(u32, 0, f4); + check(1); + SIMD.Float32x4.store1(i16, 0, f4); + check(1); + SIMD.Float32x4.store1(u16, 0, f4); + check(1); + SIMD.Float32x4.store1(i8, 0, f4); + check(1); + SIMD.Float32x4.store1(u8, 0, f4); + check(1); + } + + function testStore2() { + SIMD.Float32x4.store2(f64, 0, f4); + check(2); + SIMD.Float32x4.store2(f32, 0, f4); + check(2); + SIMD.Float32x4.store2(i32, 0, f4); + check(2); + SIMD.Float32x4.store2(u32, 0, f4); + check(2); + SIMD.Float32x4.store2(i16, 0, f4); + check(2); + SIMD.Float32x4.store2(u16, 0, f4); + check(2); + SIMD.Float32x4.store2(i8, 0, f4); + check(2); + SIMD.Float32x4.store2(u8, 0, f4); + check(2); + } + + function testStore3() { + SIMD.Float32x4.store3(f64, 0, f4); + check(3); + SIMD.Float32x4.store3(f32, 0, f4); + check(3); + SIMD.Float32x4.store3(i32, 0, f4); + check(3); + SIMD.Float32x4.store3(u32, 0, f4); + check(3); + SIMD.Float32x4.store3(i16, 0, f4); + check(3); + SIMD.Float32x4.store3(u16, 0, f4); + check(3); + SIMD.Float32x4.store3(i8, 0, f4); + check(3); + SIMD.Float32x4.store3(u8, 0, f4); + check(3); + } + + for (var i = 0; i < 150; i++) { + testStore(); + testStore1(); + testStore2(); + testStore3(); + } +} + +f(); + +function testBailout(uglyDuckling) { + var f32 = new Float32Array(16); + for (var i = 0; i < 16; i++) + f32[i] = i + 1; + + var i8 = new Int8Array(f32.buffer); + + var f4 = SIMD.Float32x4(42, 43, 44, 45); + + for (var i = 0; i < 150; i++) { + var caught = false; + try { + SIMD.Float32x4.store(i8, (i < 149) ? 0 : (16 << 2) - (4 << 2) + 1, f4); + } catch (e) { + print(e); + assertEq(e instanceof RangeError, true); + caught = true; + } + assertEq(i < 149 || caught, true); + } +} + +print('Testing range checks...'); +testBailout(-1); +testBailout(-15); +testBailout(12 * 4 + 1); diff --git a/js/src/jit-test/tests/SIMD/swizzle.js b/js/src/jit-test/tests/SIMD/swizzle.js new file mode 100644 index 000000000000..2fd56620beb0 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/swizzle.js @@ -0,0 +1,104 @@ +if (!this.hasOwnProperty("SIMD")) + quit(); + +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function f() { + var i4 = SIMD.Int32x4(1, 2, 3, 4); + + var leet = Math.fround(13.37); + var f4 = SIMD.Float32x4(-.5, -0, Infinity, leet); + + var compI = [ + [1,2,3,4], + [2,3,4,1], + [3,4,1,2], + [4,1,2,3] + ]; + + var compF = [ + [-.5, -0, Infinity, leet], + [-0, Infinity, leet, -.5], + [Infinity, leet, -.5, -0], + [leet, -.5, -0, Infinity] + ]; + + for (var i = 0; i < 150; i++) { + // Variable lanes + var r = SIMD.Float32x4.swizzle(f4, i % 4, (i + 1) % 4, (i + 2) % 4, (i + 3) % 4); + assertEqX4(r, compF[i % 4]); + + // Constant lanes + assertEqX4(SIMD.Float32x4.swizzle(f4, 3, 2, 1, 0), [leet, Infinity, -0, -.5]); + + // Variable lanes + var r = SIMD.Int32x4.swizzle(i4, i % 4, (i + 1) % 4, (i + 2) % 4, (i + 3) % 4); + assertEqX4(r, compI[i % 4]); + + // Constant lanes + assertEqX4(SIMD.Int32x4.swizzle(i4, 3, 2, 1, 0), [4, 3, 2, 1]); + } +} + +function testBailouts(expectException, uglyDuckling) { + var i4 = SIMD.Int32x4(1, 2, 3, 4); + for (var i = 0; i < 150; i++) { + // Test bailouts + var value = i == 149 ? uglyDuckling : 0; + var caught = false; + try { + assertEqX4(SIMD.Int32x4.swizzle(i4, value, 3, 2, 0), [1, 4, 3, 1]); + } catch(e) { + print(e); + caught = true; + assertEq(i, 149); + assertEq(e instanceof TypeError || e instanceof RangeError, true); + } + if (i == 149) + assertEq(caught, expectException); + } +} + +function testInt32x4SwizzleBailout() { + // Test out-of-bounds non-constant indices. This is expected to throw. + var i4 = SIMD.Int32x4(1, 2, 3, 4); + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Int32x4.swizzle(i4, i, 3, 2, 0), [i + 1, 4, 3, 1]); + } +} + +f(); +testBailouts(true, -1); +testBailouts(true, 4); +testBailouts(true, 2.5); +testBailouts(true, undefined); +testBailouts(true, {}); +testBailouts(true, 'one'); +testBailouts(false, false); +testBailouts(false, null); +testBailouts(false, " 0.0 "); + +try { + testInt32x4SwizzleBailout(); + throw 'not caught'; +} catch(e) { + assertEq(e instanceof RangeError, true); +} + +(function() { + var zappa = 0; + + function testBailouts() { + var i4 = SIMD.Int32x4(1, 2, 3, 4); + for (var i = 0; i < 300; i++) { + var value = i == 299 ? 2.5 : 1; + SIMD.Int32x4.swizzle(i4, value, 3, 2, 0); + zappa = i; + } + } + + try { testBailouts(); } catch (e) {} + assertEq(zappa, 298); +})(); diff --git a/js/src/jit-test/tests/SIMD/uconvert.js b/js/src/jit-test/tests/SIMD/uconvert.js new file mode 100644 index 000000000000..a45fd7af9cdf --- /dev/null +++ b/js/src/jit-test/tests/SIMD/uconvert.js @@ -0,0 +1,86 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 30); + +// Testing Uint32 <-> Float32 conversions. +// These conversions deserve special attention because SSE doesn't provide +// simple conversion instructions. + +// Convert an Uint32Array to a Float32Array using scalar conversions. +function cvt_utof_scalar(u32s, f32s) { + assertEq(u32s.length, f32s.length); + for (var i = 0; i < u32s.length; i++) { + f32s[i] = u32s[i]; + } +} + +// Convert an Uint32Array to a Float32Array using simd conversions. +function cvt_utof_simd(u32s, f32s) { + assertEq(u32s.length, f32s.length); + for (var i = 0; i < u32s.length; i += 4) { + SIMD.Float32x4.store(f32s, i, SIMD.Float32x4.fromUint32x4(SIMD.Uint32x4.load(u32s, i))); + } +} + +// Convert a Float32Array to an Uint32Array using scalar conversions. +function cvt_ftou_scalar(f32s, u32s) { + assertEq(f32s.length, u32s.length); + for (var i = 0; i < f32s.length; i++) { + u32s[i] = f32s[i]; + } +} + +// Convert a Float32Array to an Uint32Array using simd conversions. +function cvt_ftou_simd(f32s, u32s) { + assertEq(f32s.length, u32s.length); + for (var i = 0; i < f32s.length; i += 4) { + SIMD.Uint32x4.store(u32s, i, SIMD.Uint32x4.fromFloat32x4(SIMD.Float32x4.load(f32s, i))); + } +} + +function check(a, b) { + assertEq(a.length, b.length); + for (var i = 0; i < a.length; i++) { + assertEq(a[i], b[i]); + } +} + +// Uint32x4 --> Float32x4 tests. +var src = new Uint32Array(8000); +var dst1 = new Float32Array(8000); +var dst2 = new Float32Array(8000); + +for (var i = 0; i < 2000; i++) { + src[i] = i; + src[i + 2000] = 0x7fffffff - i; + src[i + 4000] = 0x80000000 + i; + src[i + 6000] = 0xffffffff - i; +} + +for (var n = 0; n < 10; n++) { + cvt_utof_scalar(src, dst1); + cvt_utof_simd(src, dst2); + check(dst1, dst2); +} + +// Float32x4 --> Uint32x4 tests. +var fsrc = dst1; +var fdst1 = new Uint32Array(8000); +var fdst2 = new Uint32Array(8000); + +// The 0xffffffff entries in fsrc round to 0x1.0p32f which throws. +// Go as high as 0x0.ffffffp32f. +for (var i = 0; i < 2000; i++) { + fsrc[i + 6000] = 0xffffff7f - i; +} + +// Truncation towards 0. +fsrc[1990] = -0.9 +fsrc[1991] = 0.9 +fsrc[1992] = 1.9 + +for (var n = 0; n < 10; n++) { + cvt_ftou_scalar(fsrc, fdst1); + cvt_ftou_simd(fsrc, fdst2); + check(fdst1, fdst2); +} diff --git a/js/src/jit-test/tests/SIMD/unary.js b/js/src/jit-test/tests/SIMD/unary.js new file mode 100644 index 000000000000..34ec3fb10020 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/unary.js @@ -0,0 +1,35 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +var notf = (function() { + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + return function(x) { + f32[0] = x; + i32[0] = ~i32[0]; + return f32[0]; + } +})(); + +function f() { + var f4 = SIMD.Float32x4(1, 2, 3, 4); + var i4 = SIMD.Int32x4(1, 2, 3, 4); + var b4 = SIMD.Bool32x4(true, false, true, false); + var BitOrZero = (x) => x | 0; + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.Float32x4.neg(f4), unaryX4((x) => -x, f4, Math.fround)); + assertEqX4(SIMD.Float32x4.abs(f4), unaryX4(Math.abs, f4, Math.fround)); + assertEqX4(SIMD.Float32x4.sqrt(f4), unaryX4(Math.sqrt, f4, Math.fround)); + + assertEqX4(SIMD.Float32x4.reciprocalApproximation(f4), unaryX4((x) => 1 / x, f4, Math.fround), assertNear); + assertEqX4(SIMD.Float32x4.reciprocalSqrtApproximation(f4), unaryX4((x) => 1 / Math.sqrt(x), f4, Math.fround), assertNear); + + assertEqX4(SIMD.Int32x4.not(i4), unaryX4((x) => ~x, i4, BitOrZero)); + assertEqX4(SIMD.Int32x4.neg(i4), unaryX4((x) => -x, i4, BitOrZero)); + + assertEqX4(SIMD.Bool32x4.not(b4), unaryX4((x) => !x, b4, (x) => x )); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/unbox.js b/js/src/jit-test/tests/SIMD/unbox.js new file mode 100644 index 000000000000..2fffddd06671 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/unbox.js @@ -0,0 +1,144 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("baseline.warmup.trigger", 10); +setJitCompilerOption("ion.warmup.trigger", 30); + +var max = 40, pivot = 35; + +var i32x4 = SIMD.Int32x4; +var f32x4 = SIMD.Float32x4; +var i32x4Add = SIMD.Int32x4.add; + +var FakeSIMDType = function (o) { this.x = o.x; this.y = o.y; this.z = o.z; this.w = o.w; }; +if (this.hasOwnProperty("TypedObject")) { + var TO = TypedObject; + FakeSIMDType = new TO.StructType({ x: TO.int32, y: TO.int32, z: TO.int32, w: TO.int32 }); +} + +function simdunbox_bail_undef(i, lhs, rhs) { + return i32x4Add(lhs, rhs); +} + +function simdunbox_bail_object(i, lhs, rhs) { + return i32x4Add(lhs, rhs); +} + +function simdunbox_bail_typeobj(i, lhs, rhs) { + return i32x4Add(lhs, rhs); +} + +function simdunbox_bail_badsimd(i, lhs, rhs) { + return i32x4Add(lhs, rhs); +} + +var arr_undef = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ]; +var fail_undef = 0; +var arr_object = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ]; +var fail_object = 0; +var arr_typeobj = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ]; +var fail_typeobj = 0; +var arr_badsimd = [ i32x4(0, 1, 1, 2), i32x4(1, 1, 2, 3) ]; +var fail_badsimd = 0; +for (var i = 0; i < max; i++) { + try { + arr_undef[i + 2] = simdunbox_bail_undef(i, arr_undef[i], arr_undef[i + 1]); + } catch (x) { + arr_undef[i + 2] = arr_undef[i - 1]; + fail_undef++; + } + + try { + arr_object[i + 2] = simdunbox_bail_object(i, arr_object[i], arr_object[i + 1]); + } catch (x) { + arr_object[i + 2] = arr_object[i - 1]; + fail_object++; + } + + try { + arr_typeobj[i + 2] = simdunbox_bail_typeobj(i, arr_typeobj[i], arr_typeobj[i + 1]); + } catch (x) { + arr_typeobj[i + 2] = arr_typeobj[i - 1]; + fail_typeobj++; + } + + try { + arr_badsimd[i + 2] = simdunbox_bail_badsimd(i, arr_badsimd[i], arr_badsimd[i + 1]); + } catch (x) { + arr_badsimd[i + 2] = arr_badsimd[i - 1]; + fail_badsimd++; + } + + if (i + 2 == pivot) { + arr_undef[pivot] = undefined; + arr_object[pivot] = { x: 0, y: 1, z: 2, w: 3 }; + arr_typeobj[pivot] = new FakeSIMDType({ x: 0, y: 1, z: 2, w: 3 }); + arr_badsimd[pivot] = f32x4(0, 1, 2, 3); + } +} + +assertEq(fail_undef, 2); +assertEq(fail_object, 2); +assertEq(fail_typeobj, 2); +assertEq(fail_badsimd, 2); + +// Assert that all SIMD values are correct. +function assertEqX4(real, expected, assertFunc) { + if (typeof assertFunc === 'undefined') + assertFunc = assertEq; + + assertFunc(real.x, expected[0]); + assertFunc(real.y, expected[1]); + assertFunc(real.z, expected[2]); + assertFunc(real.w, expected[3]); +} + +var fib = [0, 1]; +for (i = 0; i < max + 5; i++) + fib[i+2] = (fib[i] + fib[i+1]) | 0; + +for (i = 0; i < max; i++) { + if (i == pivot) + continue; + var ref = fib.slice(i < pivot ? i : i - 3); + assertEqX4(arr_undef[i], ref); + assertEqX4(arr_object[i], ref); + assertEqX4(arr_typeobj[i], ref); + assertEqX4(arr_badsimd[i], ref); +} + +// Check that unbox operations aren't removed +(function() { + + function add(i, v, w) { + if (i % 2 == 0) { + SIMD.Int32x4.add(v, w); + } else { + SIMD.Float32x4.add(v, w); + } + } + + var i = 0; + var caught = false; + var f4 = SIMD.Float32x4(1,2,3,4); + var i4 = SIMD.Int32x4(1,2,3,4); + try { + for (; i < 200; i++) { + if (i % 2 == 0) { + add(i, i4, i4); + } else if (i == 199) { + add(i, i4, f4); + } else { + add(i, f4, f4); + } + } + } catch(e) { + print(e); + assertEq(e instanceof TypeError, true); + assertEq(i, 199); + caught = true; + } + + assertEq(i < 199 || caught, true); + +})(); + diff --git a/js/src/jit-test/tests/asm.js/bug1126251.js b/js/src/jit-test/tests/asm.js/bug1126251.js index 77aa56dbeb37..8470a977272e 100644 --- a/js/src/jit-test/tests/asm.js/bug1126251.js +++ b/js/src/jit-test/tests/asm.js/bug1126251.js @@ -13,3 +13,41 @@ var v = asmLink(asmCompile('global', ` `), this)(); assertEq(v, NaN); + +if (!isSimdAvailable() || typeof SIMD === 'undefined') { + quit(0); +} + +var v = asmLink(asmCompile('global', ` + "use asm"; + var frd = global.Math.fround; + var Float32x4 = global.SIMD.Float32x4; + var splat = Float32x4.splat; + var ext = Float32x4.extractLane; + function e() { + var v = Float32x4(0,0,0,0); + var x = frd(0.); + v = splat(.1e+71); + x = ext(v,0); + x = frd(x / x); + return +x; + } + return e; +`), this)(); + +assertEq(v, NaN); + +// Bug 1130618: without GVN +setJitCompilerOption("ion.gvn.enable", 0); +var v = asmLink(asmCompile('global', ` + "use asm"; + var Float32x4 = global.SIMD.Float32x4; + var splat = Float32x4.splat; + var ext = Float32x4.extractLane; + function e() { + return +ext(splat(.1e+71),0); + } + return e; +`), this)(); + +assertEq(v, Infinity); diff --git a/js/src/jit-test/tests/asm.js/bug1201124-simd-proxy.js b/js/src/jit-test/tests/asm.js/bug1201124-simd-proxy.js new file mode 100644 index 000000000000..edcc069ffb86 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/bug1201124-simd-proxy.js @@ -0,0 +1,28 @@ +load(libdir + "asm.js"); +load(libdir + "asserts.js"); + +if (typeof newGlobal !== 'function' || + !isSimdAvailable() || + typeof SIMD === 'undefined') +{ + quit(); +} + +var stdlib = new (newGlobal().Proxy)(this, new Proxy({ + simdGet: 0, + getOwnPropertyDescriptor(t, pk) { + if (pk === "SIMD" && this.simdGet++ === 1) { + return {}; + } + return Reflect.getOwnPropertyDescriptor(t, pk); + } +}, { + get(t, pk, r) { + print("trap", pk); + return Reflect.get(t, pk, r); + } +})); + +var m = asmCompile('stdlib', '"use asm"; var i4=stdlib.SIMD.Int32x4; var i4add=i4.add; return {}'); + +assertAsmLinkFail(m, stdlib); diff --git a/js/src/jit-test/tests/asm.js/simd-fbirds.js b/js/src/jit-test/tests/asm.js/simd-fbirds.js new file mode 100644 index 000000000000..97b7e29741c9 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/simd-fbirds.js @@ -0,0 +1,198 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ; js-indent-level : 2 ; js-curly-indent-offset: 0 -*- */ +/* vim: set ts=4 et sw=4 tw=80: */ + +// Author: Peter Jensen + +load(libdir + "asm.js"); +if (!isSimdAvailable() || typeof SIMD === 'undefined') { + print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +const NUM_BIRDS = 30; +const NUM_UPDATES = 20; +const ACCEL_DATA_STEPS = 30; + +var buffer = new ArrayBuffer(0x200000); +var bufferF32 = new Float32Array(buffer); + +var actualBirds = 0; + +function init() { + actualBirds = 0; + // Make it a power of two, for quick modulo wrapping. + var accelDataValues = [10.0, 9.5, 9.0, 8.0, 7.0, 6.0, 5.5, 5.0, 5.0, 5.0, 5.5, 6.0, 7.0, 8.0, 9.0, 10.0]; + accelDataValues = accelDataValues.map(function(v) { return 50*v; }); + var accelDataValuesLength = accelDataValues.length; + assertEq(accelDataValuesLength, 16); // Hard coded in the asm.js module + for (i = 0; i < accelDataValuesLength; i++) + bufferF32[i + NUM_BIRDS * 2] = accelDataValues[i]; +} + +function addBird(pos, vel) { + bufferF32[actualBirds] = pos; + bufferF32[actualBirds + NUM_BIRDS] = vel; + actualBirds++; + return actualBirds - 1; +} + +function getActualBirds() { + return actualBirds; +} + +var code = ` + "use asm"; + var toF = global.Math.fround; + var u8 = new global.Uint8Array(buffer); + var f32 = new global.Float32Array(buffer); + const maxBirds = 100000; + const maxBirdsx4 = 400000; + const maxBirdsx8 = 800000; + const accelMask = 0x3c; + const mk4 = 0x000ffff0; + + const getMaxPos = 1000.0; + const getAccelDataSteps = imp.accelDataSteps | 0; + var getActualBirds = imp.getActualBirds; + + var i4 = global.SIMD.Int32x4; + var f4 = global.SIMD.Float32x4; + var b4 = global.SIMD.Bool32x4; + var i4add = i4.add; + var i4and = i4.and; + var f4select = f4.select; + var f4add = f4.add; + var f4sub = f4.sub; + var f4mul = f4.mul; + var f4greaterThan = f4.greaterThan; + var f4splat = f4.splat; + var f4load = f4.load; + var f4store = f4.store; + var b4any = b4.anyTrue; + + const zerox4 = f4(0.0,0.0,0.0,0.0); + + function declareHeapSize() { + f32[0x0007ffff] = toF(0.0); + } + + function update(timeDelta) { + timeDelta = toF(timeDelta); + // var steps = Math.ceil(timeDelta/accelData.interval); + var steps = 0; + var subTimeDelta = toF(0.0); + var actualBirds = 0; + var maxPos = toF(0.0); + var maxPosx4 = f4(0.0,0.0,0.0,0.0); + var subTimeDeltax4 = f4(0.0,0.0,0.0,0.0); + var subTimeDeltaSquaredx4 = f4(0.0,0.0,0.0,0.0); + var point5x4 = f4(0.5, 0.5, 0.5, 0.5); + var i = 0; + var len = 0; + var accelIndex = 0; + var newPosx4 = f4(0.0,0.0,0.0,0.0); + var newVelx4 = f4(0.0,0.0,0.0,0.0); + var accel = toF(0.0); + var accelx4 = f4(0.0,0.0,0.0,0.0); + var a = 0; + var posDeltax4 = f4(0.0,0.0,0.0,0.0); + var cmpx4 = b4(0,0,0,0); + var newVelTruex4 = f4(0.0,0.0,0.0,0.0); + + steps = getAccelDataSteps | 0; + subTimeDelta = toF(toF(timeDelta / toF(steps | 0)) / toF(1000.0)); + actualBirds = getActualBirds() | 0; + maxPos = toF(+getMaxPos); + maxPosx4 = f4splat(maxPos); + subTimeDeltax4 = f4splat(subTimeDelta); + subTimeDeltaSquaredx4 = f4mul(subTimeDeltax4, subTimeDeltax4); + + len = ((actualBirds + 3) >> 2) << 4; + + for (i = 0; (i | 0) < (len | 0); i = (i + 16) | 0) { + accelIndex = 0; + newPosx4 = f4load(u8, i & mk4); + newVelx4 = f4load(u8, (i & mk4) + maxBirdsx4); + for (a = 0; (a | 0) < (steps | 0); a = (a + 1) | 0) { + accel = toF(f32[(accelIndex & accelMask) + maxBirdsx8 >> 2]); + accelx4 = f4splat(accel); + accelIndex = (accelIndex + 4) | 0; + posDeltax4 = f4mul(point5x4, f4mul(accelx4, subTimeDeltaSquaredx4)); + posDeltax4 = f4add(posDeltax4, f4mul(newVelx4, subTimeDeltax4)); + newPosx4 = f4add(newPosx4, posDeltax4); + newVelx4 = f4add(newVelx4, f4mul(accelx4, subTimeDeltax4)); + cmpx4 = f4greaterThan(newPosx4, maxPosx4); + + if (b4any(cmpx4)) { + // Work around unimplemented 'neg' operation, using 0 - x. + newVelTruex4 = f4sub(zerox4, newVelx4); + newVelx4 = f4select(cmpx4, newVelTruex4, newVelx4); + } + } + f4store(u8, i & mk4, newPosx4); + f4store(u8, (i & mk4) + maxBirdsx4, newVelx4); + } + } + + return update; +` + +var ffi = { + getActualBirds, + accelDataSteps: ACCEL_DATA_STEPS +}; + +var fbirds = asmLink(asmCompile('global', 'imp', 'buffer', code), this, ffi, buffer); + +init(); +for (var i = 0; i < NUM_BIRDS; i++) { + addBird(i / 10, Math.exp(2, NUM_BIRDS - i)); +} + +var b = dateNow(); +for (var j = 0; j < NUM_UPDATES; j++) { + fbirds(16); +} +print(dateNow() - b); + +assertEq(bufferF32[0], 0); +assertEq(bufferF32[1], 0.10000000149011612); +assertEq(bufferF32[2], 0.20000000298023224); +assertEq(bufferF32[3], 0.30000001192092896); +assertEq(bufferF32[4], 0.4000000059604645); +assertEq(bufferF32[5], 0.5); +assertEq(bufferF32[6], 0.6000000238418579); +assertEq(bufferF32[7], 0.699999988079071); +assertEq(bufferF32[8], 0.800000011920929); +assertEq(bufferF32[9], 0.8999999761581421); +assertEq(bufferF32[10], 1); +assertEq(bufferF32[11], 1.100000023841858); +assertEq(bufferF32[12], 1.2000000476837158); +assertEq(bufferF32[13], 1.2999999523162842); +assertEq(bufferF32[14], 1.399999976158142); +assertEq(bufferF32[15], 1.5); +assertEq(bufferF32[16], 1.600000023841858); +assertEq(bufferF32[17], 1.7000000476837158); +assertEq(bufferF32[18], 1.7999999523162842); +assertEq(bufferF32[19], 1.899999976158142); +assertEq(bufferF32[20], 2); +assertEq(bufferF32[21], 2.0999999046325684); +assertEq(bufferF32[22], 2.200000047683716); +assertEq(bufferF32[23], 2.299999952316284); +assertEq(bufferF32[24], 2.4000000953674316); +assertEq(bufferF32[25], 2.5); +assertEq(bufferF32[26], 2.5999999046325684); +assertEq(bufferF32[27], 2.700000047683716); +assertEq(bufferF32[28], 2.799999952316284); +assertEq(bufferF32[29], 2.9000000953674316); + + +// Code used to generate the assertEq list above. +function generateAssertList() { + var buf = ''; + for (var k = 0; k < NUM_BIRDS; k++) { + buf += 'assertEq(bufferF32['+ k + '], ' + bufferF32[k] + ');\n'; + } + print(buf); +} +//generateAssertList(); diff --git a/js/src/jit-test/tests/asm.js/simd-mandelbrot.js b/js/src/jit-test/tests/asm.js/simd-mandelbrot.js new file mode 100644 index 000000000000..349f2062d63e --- /dev/null +++ b/js/src/jit-test/tests/asm.js/simd-mandelbrot.js @@ -0,0 +1,1819 @@ +/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ; js-indent-level : 2 ; js-curly-indent-offset: 0 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ + +// Mandelbrot using SIMD +// Author: Peter Jensen, Intel Corporation + +load(libdir + "asm.js"); +if (!isSimdAvailable() || typeof SIMD === 'undefined') { + print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +// global variables +const MAX_ITERATIONS = 10; +const DRAW_ITERATIONS = 10; + +const CANVAS_WIDTH = 20; +const CANVAS_HEIGHT = 20; + +const LIMIT_SHOW = 20 * 20 * 4; + +// Asm.js module buffer. +var buffer = new ArrayBuffer(16 * 1024 * 1024); +var view = new Uint8Array(buffer); + +var moduleCode = ` + "use asm" + var b8 = new global.Uint8Array(buffer); + var toF = global.Math.fround; + var i4 = global.SIMD.Int32x4; + var ci4 = i4.check; + var f4 = global.SIMD.Float32x4; + var i4add = i4.add; + var i4and = i4.and; + var i4ext = i4.extractLane; + var i4sel = i4.select; + var f4add = f4.add; + var f4sub = f4.sub; + var f4mul = f4.mul; + var f4lessThanOrEqual = f4.lessThanOrEqual; + var f4splat = f4.splat; + var imul = global.Math.imul; + var b4 = global.SIMD.Bool32x4; + var b4any = b4.anyTrue; + const zero4 = i4(0,0,0,0), one4 = i4(1,1,1,1), two4 = f4(2,2,2,2), four4 = f4(4,4,4,4); + + const mk0 = 0x007fffff; + + function declareHeapLength() { + b8[0x00ffffff] = 0; + } + + function mapColorAndSetPixel (x, y, width, value, max_iterations) { + x = x | 0; + y = y | 0; + width = width | 0; + value = value | 0; + max_iterations = max_iterations | 0; + + var rgb = 0, r = 0, g = 0, b = 0, index = 0; + + index = (((imul((width >>> 0), (y >>> 0)) + x) | 0) * 4) | 0; + if ((value | 0) == (max_iterations | 0)) { + r = 0; + g = 0; + b = 0; + } else { + rgb = ~~toF(toF(toF(toF(value >>> 0) * toF(0xffff)) / toF(max_iterations >>> 0)) * toF(0xff)); + r = rgb & 0xff; + g = (rgb >>> 8) & 0xff; + b = (rgb >>> 16) & 0xff; + } + b8[(index & mk0) >> 0] = r; + b8[(index & mk0) + 1 >> 0] = g; + b8[(index & mk0) + 2 >> 0] = b; + b8[(index & mk0) + 3 >> 0] = 255; + } + + function mandelPixelX4 (xf, yf, yd, max_iterations) { + xf = toF(xf); + yf = toF(yf); + yd = toF(yd); + max_iterations = max_iterations | 0; + var c_re4 = f4(0,0,0,0), c_im4 = f4(0,0,0,0); + var z_re4 = f4(0,0,0,0), z_im4 = f4(0,0,0,0); + var count4 = i4(0,0,0,0); + var z_re24 = f4(0,0,0,0), z_im24 = f4(0,0,0,0); + var new_re4 = f4(0,0,0,0), new_im4 = f4(0,0,0,0); + var i = 0; + var mb4 = b4(0,0,0,0); + + c_re4 = f4splat(xf); + c_im4 = f4(yf, toF(yd + yf), toF(yd + toF(yd + yf)), toF(yd + toF(yd + toF(yd + yf)))); + + z_re4 = c_re4; + z_im4 = c_im4; + + for (i = 0; (i | 0) < (max_iterations | 0); i = (i + 1) | 0) { + z_re24 = f4mul(z_re4, z_re4); + z_im24 = f4mul(z_im4, z_im4); + mb4 = f4lessThanOrEqual(f4add(z_re24, z_im24), four4); + // If all 4 values are greater than 4.0, there's no reason to continue. + if (!b4any(mb4)) + break; + + new_re4 = f4sub(z_re24, z_im24); + new_im4 = f4mul(f4mul(two4, z_re4), z_im4); + z_re4 = f4add(c_re4, new_re4); + z_im4 = f4add(c_im4, new_im4); + count4 = i4add(count4, i4sel(mb4, one4, zero4)); + } + return ci4(count4); + } + + function mandelColumnX4 (x, width, height, xf, yf, yd, max_iterations) { + x = x | 0; + width = width | 0; + height = height | 0; + xf = toF(xf); + yf = toF(yf); + yd = toF(yd); + max_iterations = max_iterations | 0; + + var y = 0; + var ydx4 = toF(0); + var m4 = i4(0,0,0,0); + + ydx4 = toF(yd * toF(4)); + for (y = 0; (y | 0) < (height | 0); y = (y + 4) | 0) { + m4 = ci4(mandelPixelX4(toF(xf), toF(yf), toF(yd), max_iterations)); + mapColorAndSetPixel(x | 0, y | 0, width, i4ext(m4,0), max_iterations); + mapColorAndSetPixel(x | 0, (y + 1) | 0, width, i4ext(m4,1), max_iterations); + mapColorAndSetPixel(x | 0, (y + 2) | 0, width, i4ext(m4,2), max_iterations); + mapColorAndSetPixel(x | 0, (y + 3) | 0, width, i4ext(m4,3), max_iterations); + yf = toF(yf + ydx4); + } + } + + function mandel (width, height, xc, yc, scale, max_iterations) { + width = width | 0; + height = height | 0; + xc = toF(xc); + yc = toF(yc); + scale = toF(scale); + max_iterations = max_iterations | 0; + + var x0 = toF(0), y0 = toF(0); + var xd = toF(0), yd = toF(0); + var xf = toF(0); + var x = 0; + + x0 = toF(xc - toF(scale * toF(1.5))); + y0 = toF(yc - scale); + xd = toF(toF(scale * toF(3)) / toF(width >>> 0)); + yd = toF(toF(scale * toF(2)) / toF(height >>> 0)); + xf = x0; + + for (x = 0; (x | 0) < (width | 0); x = (x + 1) | 0) { + mandelColumnX4(x, width, height, xf, y0, yd, max_iterations); + xf = toF(xf + xd); + } + } + + return mandel; +`; + +var FFI = {}; +var mandelbro = asmLink(asmCompile('global', 'ffi', 'buffer', moduleCode), this, FFI, buffer); + +function animateMandelbrot () { + var scale_start = 1.0; + var scale_end = 0.0005; + var xc_start = -0.5; + var yc_start = 0.0; + var xc_end = 0.0; + var yc_end = 0.75; + var steps = 200.0; + var scale_step = (scale_end - scale_start)/steps; + var xc_step = (xc_end - xc_start)/steps; + var yc_step = (yc_end - yc_start)/steps; + var scale = scale_start; + var xc = xc_start; + var yc = yc_start; + var i = 0; + var now = dateNow(); + + function draw1 () { + mandelbro(CANVAS_WIDTH, CANVAS_HEIGHT, xc, yc, scale, MAX_ITERATIONS); + if (scale < scale_end || scale > scale_start) { + scale_step = -scale_step; + xc_step = -xc_step; + yc_step = -yc_step; + } + scale += scale_step; + xc += xc_step; + yc += yc_step; + i++; + } + + var b = dateNow(); + for (var j = DRAW_ITERATIONS; j --> 0;) + draw1(); + print(dateNow() - b); +} + +animateMandelbrot(); + +assertEq(view[0], 0, "0th value should be 0"); +assertEq(view[1], 0, "1th value should be 0"); +assertEq(view[2], 0, "2th value should be 0"); +assertEq(view[3], 255, "3th value should be 255"); +assertEq(view[4], 230, "4th value should be 230"); +assertEq(view[5], 127, "5th value should be 127"); +assertEq(view[6], 25, "6th value should be 25"); +assertEq(view[7], 255, "7th value should be 255"); +assertEq(view[8], 230, "8th value should be 230"); +assertEq(view[9], 127, "9th value should be 127"); +assertEq(view[10], 25, "10th value should be 25"); +assertEq(view[11], 255, "11th value should be 255"); +assertEq(view[12], 205, "12th value should be 205"); +assertEq(view[13], 255, "13th value should be 255"); +assertEq(view[14], 50, "14th value should be 50"); +assertEq(view[15], 255, "15th value should be 255"); +assertEq(view[16], 205, "16th value should be 205"); +assertEq(view[17], 255, "17th value should be 255"); +assertEq(view[18], 50, "18th value should be 50"); +assertEq(view[19], 255, "19th value should be 255"); +assertEq(view[20], 205, "20th value should be 205"); +assertEq(view[21], 255, "21th value should be 255"); +assertEq(view[22], 50, "22th value should be 50"); +assertEq(view[23], 255, "23th value should be 255"); +assertEq(view[24], 205, "24th value should be 205"); +assertEq(view[25], 255, "25th value should be 255"); +assertEq(view[26], 50, "26th value should be 50"); +assertEq(view[27], 255, "27th value should be 255"); +assertEq(view[28], 205, "28th value should be 205"); +assertEq(view[29], 255, "29th value should be 255"); +assertEq(view[30], 50, "30th value should be 50"); +assertEq(view[31], 255, "31th value should be 255"); +assertEq(view[32], 179, "32th value should be 179"); +assertEq(view[33], 127, "33th value should be 127"); +assertEq(view[34], 76, "34th value should be 76"); +assertEq(view[35], 255, "35th value should be 255"); +assertEq(view[36], 179, "36th value should be 179"); +assertEq(view[37], 127, "37th value should be 127"); +assertEq(view[38], 76, "38th value should be 76"); +assertEq(view[39], 255, "39th value should be 255"); +assertEq(view[40], 179, "40th value should be 179"); +assertEq(view[41], 127, "41th value should be 127"); +assertEq(view[42], 76, "42th value should be 76"); +assertEq(view[43], 255, "43th value should be 255"); +assertEq(view[44], 154, "44th value should be 154"); +assertEq(view[45], 255, "45th value should be 255"); +assertEq(view[46], 101, "46th value should be 101"); +assertEq(view[47], 255, "47th value should be 255"); +assertEq(view[48], 78, "48th value should be 78"); +assertEq(view[49], 127, "49th value should be 127"); +assertEq(view[50], 178, "50th value should be 178"); +assertEq(view[51], 255, "51th value should be 255"); +assertEq(view[52], 52, "52th value should be 52"); +assertEq(view[53], 255, "53th value should be 255"); +assertEq(view[54], 203, "54th value should be 203"); +assertEq(view[55], 255, "55th value should be 255"); +assertEq(view[56], 154, "56th value should be 154"); +assertEq(view[57], 255, "57th value should be 255"); +assertEq(view[58], 101, "58th value should be 101"); +assertEq(view[59], 255, "59th value should be 255"); +assertEq(view[60], 179, "60th value should be 179"); +assertEq(view[61], 127, "61th value should be 127"); +assertEq(view[62], 76, "62th value should be 76"); +assertEq(view[63], 255, "63th value should be 255"); +assertEq(view[64], 205, "64th value should be 205"); +assertEq(view[65], 255, "65th value should be 255"); +assertEq(view[66], 50, "66th value should be 50"); +assertEq(view[67], 255, "67th value should be 255"); +assertEq(view[68], 205, "68th value should be 205"); +assertEq(view[69], 255, "69th value should be 255"); +assertEq(view[70], 50, "70th value should be 50"); +assertEq(view[71], 255, "71th value should be 255"); +assertEq(view[72], 230, "72th value should be 230"); +assertEq(view[73], 127, "73th value should be 127"); +assertEq(view[74], 25, "74th value should be 25"); +assertEq(view[75], 255, "75th value should be 255"); +assertEq(view[76], 230, "76th value should be 230"); +assertEq(view[77], 127, "77th value should be 127"); +assertEq(view[78], 25, "78th value should be 25"); +assertEq(view[79], 255, "79th value should be 255"); +assertEq(view[80], 0, "80th value should be 0"); +assertEq(view[81], 0, "81th value should be 0"); +assertEq(view[82], 0, "82th value should be 0"); +assertEq(view[83], 255, "83th value should be 255"); +assertEq(view[84], 230, "84th value should be 230"); +assertEq(view[85], 127, "85th value should be 127"); +assertEq(view[86], 25, "86th value should be 25"); +assertEq(view[87], 255, "87th value should be 255"); +assertEq(view[88], 205, "88th value should be 205"); +assertEq(view[89], 255, "89th value should be 255"); +assertEq(view[90], 50, "90th value should be 50"); +assertEq(view[91], 255, "91th value should be 255"); +assertEq(view[92], 205, "92th value should be 205"); +assertEq(view[93], 255, "93th value should be 255"); +assertEq(view[94], 50, "94th value should be 50"); +assertEq(view[95], 255, "95th value should be 255"); +assertEq(view[96], 205, "96th value should be 205"); +assertEq(view[97], 255, "97th value should be 255"); +assertEq(view[98], 50, "98th value should be 50"); +assertEq(view[99], 255, "99th value should be 255"); +assertEq(view[100], 205, "100th value should be 205"); +assertEq(view[101], 255, "101th value should be 255"); +assertEq(view[102], 50, "102th value should be 50"); +assertEq(view[103], 255, "103th value should be 255"); +assertEq(view[104], 205, "104th value should be 205"); +assertEq(view[105], 255, "105th value should be 255"); +assertEq(view[106], 50, "106th value should be 50"); +assertEq(view[107], 255, "107th value should be 255"); +assertEq(view[108], 205, "108th value should be 205"); +assertEq(view[109], 255, "109th value should be 255"); +assertEq(view[110], 50, "110th value should be 50"); +assertEq(view[111], 255, "111th value should be 255"); +assertEq(view[112], 179, "112th value should be 179"); +assertEq(view[113], 127, "113th value should be 127"); +assertEq(view[114], 76, "114th value should be 76"); +assertEq(view[115], 255, "115th value should be 255"); +assertEq(view[116], 179, "116th value should be 179"); +assertEq(view[117], 127, "117th value should be 127"); +assertEq(view[118], 76, "118th value should be 76"); +assertEq(view[119], 255, "119th value should be 255"); +assertEq(view[120], 154, "120th value should be 154"); +assertEq(view[121], 255, "121th value should be 255"); +assertEq(view[122], 101, "122th value should be 101"); +assertEq(view[123], 255, "123th value should be 255"); +assertEq(view[124], 103, "124th value should be 103"); +assertEq(view[125], 255, "125th value should be 255"); +assertEq(view[126], 152, "126th value should be 152"); +assertEq(view[127], 255, "127th value should be 255"); +assertEq(view[128], 0, "128th value should be 0"); +assertEq(view[129], 0, "129th value should be 0"); +assertEq(view[130], 0, "130th value should be 0"); +assertEq(view[131], 255, "131th value should be 255"); +assertEq(view[132], 0, "132th value should be 0"); +assertEq(view[133], 0, "133th value should be 0"); +assertEq(view[134], 0, "134th value should be 0"); +assertEq(view[135], 255, "135th value should be 255"); +assertEq(view[136], 128, "136th value should be 128"); +assertEq(view[137], 127, "137th value should be 127"); +assertEq(view[138], 127, "138th value should be 127"); +assertEq(view[139], 255, "139th value should be 255"); +assertEq(view[140], 154, "140th value should be 154"); +assertEq(view[141], 255, "141th value should be 255"); +assertEq(view[142], 101, "142th value should be 101"); +assertEq(view[143], 255, "143th value should be 255"); +assertEq(view[144], 179, "144th value should be 179"); +assertEq(view[145], 127, "145th value should be 127"); +assertEq(view[146], 76, "146th value should be 76"); +assertEq(view[147], 255, "147th value should be 255"); +assertEq(view[148], 205, "148th value should be 205"); +assertEq(view[149], 255, "149th value should be 255"); +assertEq(view[150], 50, "150th value should be 50"); +assertEq(view[151], 255, "151th value should be 255"); +assertEq(view[152], 205, "152th value should be 205"); +assertEq(view[153], 255, "153th value should be 255"); +assertEq(view[154], 50, "154th value should be 50"); +assertEq(view[155], 255, "155th value should be 255"); +assertEq(view[156], 230, "156th value should be 230"); +assertEq(view[157], 127, "157th value should be 127"); +assertEq(view[158], 25, "158th value should be 25"); +assertEq(view[159], 255, "159th value should be 255"); +assertEq(view[160], 0, "160th value should be 0"); +assertEq(view[161], 0, "161th value should be 0"); +assertEq(view[162], 0, "162th value should be 0"); +assertEq(view[163], 255, "163th value should be 255"); +assertEq(view[164], 230, "164th value should be 230"); +assertEq(view[165], 127, "165th value should be 127"); +assertEq(view[166], 25, "166th value should be 25"); +assertEq(view[167], 255, "167th value should be 255"); +assertEq(view[168], 205, "168th value should be 205"); +assertEq(view[169], 255, "169th value should be 255"); +assertEq(view[170], 50, "170th value should be 50"); +assertEq(view[171], 255, "171th value should be 255"); +assertEq(view[172], 205, "172th value should be 205"); +assertEq(view[173], 255, "173th value should be 255"); +assertEq(view[174], 50, "174th value should be 50"); +assertEq(view[175], 255, "175th value should be 255"); +assertEq(view[176], 205, "176th value should be 205"); +assertEq(view[177], 255, "177th value should be 255"); +assertEq(view[178], 50, "178th value should be 50"); +assertEq(view[179], 255, "179th value should be 255"); +assertEq(view[180], 205, "180th value should be 205"); +assertEq(view[181], 255, "181th value should be 255"); +assertEq(view[182], 50, "182th value should be 50"); +assertEq(view[183], 255, "183th value should be 255"); +assertEq(view[184], 205, "184th value should be 205"); +assertEq(view[185], 255, "185th value should be 255"); +assertEq(view[186], 50, "186th value should be 50"); +assertEq(view[187], 255, "187th value should be 255"); +assertEq(view[188], 179, "188th value should be 179"); +assertEq(view[189], 127, "189th value should be 127"); +assertEq(view[190], 76, "190th value should be 76"); +assertEq(view[191], 255, "191th value should be 255"); +assertEq(view[192], 179, "192th value should be 179"); +assertEq(view[193], 127, "193th value should be 127"); +assertEq(view[194], 76, "194th value should be 76"); +assertEq(view[195], 255, "195th value should be 255"); +assertEq(view[196], 154, "196th value should be 154"); +assertEq(view[197], 255, "197th value should be 255"); +assertEq(view[198], 101, "198th value should be 101"); +assertEq(view[199], 255, "199th value should be 255"); +assertEq(view[200], 103, "200th value should be 103"); +assertEq(view[201], 255, "201th value should be 255"); +assertEq(view[202], 152, "202th value should be 152"); +assertEq(view[203], 255, "203th value should be 255"); +assertEq(view[204], 78, "204th value should be 78"); +assertEq(view[205], 127, "205th value should be 127"); +assertEq(view[206], 178, "206th value should be 178"); +assertEq(view[207], 255, "207th value should be 255"); +assertEq(view[208], 0, "208th value should be 0"); +assertEq(view[209], 0, "209th value should be 0"); +assertEq(view[210], 0, "210th value should be 0"); +assertEq(view[211], 255, "211th value should be 255"); +assertEq(view[212], 0, "212th value should be 0"); +assertEq(view[213], 0, "213th value should be 0"); +assertEq(view[214], 0, "214th value should be 0"); +assertEq(view[215], 255, "215th value should be 255"); +assertEq(view[216], 78, "216th value should be 78"); +assertEq(view[217], 127, "217th value should be 127"); +assertEq(view[218], 178, "218th value should be 178"); +assertEq(view[219], 255, "219th value should be 255"); +assertEq(view[220], 128, "220th value should be 128"); +assertEq(view[221], 127, "221th value should be 127"); +assertEq(view[222], 127, "222th value should be 127"); +assertEq(view[223], 255, "223th value should be 255"); +assertEq(view[224], 154, "224th value should be 154"); +assertEq(view[225], 255, "225th value should be 255"); +assertEq(view[226], 101, "226th value should be 101"); +assertEq(view[227], 255, "227th value should be 255"); +assertEq(view[228], 205, "228th value should be 205"); +assertEq(view[229], 255, "229th value should be 255"); +assertEq(view[230], 50, "230th value should be 50"); +assertEq(view[231], 255, "231th value should be 255"); +assertEq(view[232], 205, "232th value should be 205"); +assertEq(view[233], 255, "233th value should be 255"); +assertEq(view[234], 50, "234th value should be 50"); +assertEq(view[235], 255, "235th value should be 255"); +assertEq(view[236], 230, "236th value should be 230"); +assertEq(view[237], 127, "237th value should be 127"); +assertEq(view[238], 25, "238th value should be 25"); +assertEq(view[239], 255, "239th value should be 255"); +assertEq(view[240], 0, "240th value should be 0"); +assertEq(view[241], 0, "241th value should be 0"); +assertEq(view[242], 0, "242th value should be 0"); +assertEq(view[243], 255, "243th value should be 255"); +assertEq(view[244], 205, "244th value should be 205"); +assertEq(view[245], 255, "245th value should be 255"); +assertEq(view[246], 50, "246th value should be 50"); +assertEq(view[247], 255, "247th value should be 255"); +assertEq(view[248], 205, "248th value should be 205"); +assertEq(view[249], 255, "249th value should be 255"); +assertEq(view[250], 50, "250th value should be 50"); +assertEq(view[251], 255, "251th value should be 255"); +assertEq(view[252], 205, "252th value should be 205"); +assertEq(view[253], 255, "253th value should be 255"); +assertEq(view[254], 50, "254th value should be 50"); +assertEq(view[255], 255, "255th value should be 255"); +assertEq(view[256], 205, "256th value should be 205"); +assertEq(view[257], 255, "257th value should be 255"); +assertEq(view[258], 50, "258th value should be 50"); +assertEq(view[259], 255, "259th value should be 255"); +assertEq(view[260], 205, "260th value should be 205"); +assertEq(view[261], 255, "261th value should be 255"); +assertEq(view[262], 50, "262th value should be 50"); +assertEq(view[263], 255, "263th value should be 255"); +assertEq(view[264], 179, "264th value should be 179"); +assertEq(view[265], 127, "265th value should be 127"); +assertEq(view[266], 76, "266th value should be 76"); +assertEq(view[267], 255, "267th value should be 255"); +assertEq(view[268], 179, "268th value should be 179"); +assertEq(view[269], 127, "269th value should be 127"); +assertEq(view[270], 76, "270th value should be 76"); +assertEq(view[271], 255, "271th value should be 255"); +assertEq(view[272], 154, "272th value should be 154"); +assertEq(view[273], 255, "273th value should be 255"); +assertEq(view[274], 101, "274th value should be 101"); +assertEq(view[275], 255, "275th value should be 255"); +assertEq(view[276], 52, "276th value should be 52"); +assertEq(view[277], 255, "277th value should be 255"); +assertEq(view[278], 203, "278th value should be 203"); +assertEq(view[279], 255, "279th value should be 255"); +assertEq(view[280], 0, "280th value should be 0"); +assertEq(view[281], 0, "281th value should be 0"); +assertEq(view[282], 0, "282th value should be 0"); +assertEq(view[283], 255, "283th value should be 255"); +assertEq(view[284], 0, "284th value should be 0"); +assertEq(view[285], 0, "285th value should be 0"); +assertEq(view[286], 0, "286th value should be 0"); +assertEq(view[287], 255, "287th value should be 255"); +assertEq(view[288], 0, "288th value should be 0"); +assertEq(view[289], 0, "289th value should be 0"); +assertEq(view[290], 0, "290th value should be 0"); +assertEq(view[291], 255, "291th value should be 255"); +assertEq(view[292], 0, "292th value should be 0"); +assertEq(view[293], 0, "293th value should be 0"); +assertEq(view[294], 0, "294th value should be 0"); +assertEq(view[295], 255, "295th value should be 255"); +assertEq(view[296], 0, "296th value should be 0"); +assertEq(view[297], 0, "297th value should be 0"); +assertEq(view[298], 0, "298th value should be 0"); +assertEq(view[299], 255, "299th value should be 255"); +assertEq(view[300], 52, "300th value should be 52"); +assertEq(view[301], 255, "301th value should be 255"); +assertEq(view[302], 203, "302th value should be 203"); +assertEq(view[303], 255, "303th value should be 255"); +assertEq(view[304], 52, "304th value should be 52"); +assertEq(view[305], 255, "305th value should be 255"); +assertEq(view[306], 203, "306th value should be 203"); +assertEq(view[307], 255, "307th value should be 255"); +assertEq(view[308], 179, "308th value should be 179"); +assertEq(view[309], 127, "309th value should be 127"); +assertEq(view[310], 76, "310th value should be 76"); +assertEq(view[311], 255, "311th value should be 255"); +assertEq(view[312], 205, "312th value should be 205"); +assertEq(view[313], 255, "313th value should be 255"); +assertEq(view[314], 50, "314th value should be 50"); +assertEq(view[315], 255, "315th value should be 255"); +assertEq(view[316], 205, "316th value should be 205"); +assertEq(view[317], 255, "317th value should be 255"); +assertEq(view[318], 50, "318th value should be 50"); +assertEq(view[319], 255, "319th value should be 255"); +assertEq(view[320], 230, "320th value should be 230"); +assertEq(view[321], 127, "321th value should be 127"); +assertEq(view[322], 25, "322th value should be 25"); +assertEq(view[323], 255, "323th value should be 255"); +assertEq(view[324], 205, "324th value should be 205"); +assertEq(view[325], 255, "325th value should be 255"); +assertEq(view[326], 50, "326th value should be 50"); +assertEq(view[327], 255, "327th value should be 255"); +assertEq(view[328], 205, "328th value should be 205"); +assertEq(view[329], 255, "329th value should be 255"); +assertEq(view[330], 50, "330th value should be 50"); +assertEq(view[331], 255, "331th value should be 255"); +assertEq(view[332], 205, "332th value should be 205"); +assertEq(view[333], 255, "333th value should be 255"); +assertEq(view[334], 50, "334th value should be 50"); +assertEq(view[335], 255, "335th value should be 255"); +assertEq(view[336], 205, "336th value should be 205"); +assertEq(view[337], 255, "337th value should be 255"); +assertEq(view[338], 50, "338th value should be 50"); +assertEq(view[339], 255, "339th value should be 255"); +assertEq(view[340], 179, "340th value should be 179"); +assertEq(view[341], 127, "341th value should be 127"); +assertEq(view[342], 76, "342th value should be 76"); +assertEq(view[343], 255, "343th value should be 255"); +assertEq(view[344], 154, "344th value should be 154"); +assertEq(view[345], 255, "345th value should be 255"); +assertEq(view[346], 101, "346th value should be 101"); +assertEq(view[347], 255, "347th value should be 255"); +assertEq(view[348], 154, "348th value should be 154"); +assertEq(view[349], 255, "349th value should be 255"); +assertEq(view[350], 101, "350th value should be 101"); +assertEq(view[351], 255, "351th value should be 255"); +assertEq(view[352], 128, "352th value should be 128"); +assertEq(view[353], 127, "353th value should be 127"); +assertEq(view[354], 127, "354th value should be 127"); +assertEq(view[355], 255, "355th value should be 255"); +assertEq(view[356], 52, "356th value should be 52"); +assertEq(view[357], 255, "357th value should be 255"); +assertEq(view[358], 203, "358th value should be 203"); +assertEq(view[359], 255, "359th value should be 255"); +assertEq(view[360], 0, "360th value should be 0"); +assertEq(view[361], 0, "361th value should be 0"); +assertEq(view[362], 0, "362th value should be 0"); +assertEq(view[363], 255, "363th value should be 255"); +assertEq(view[364], 0, "364th value should be 0"); +assertEq(view[365], 0, "365th value should be 0"); +assertEq(view[366], 0, "366th value should be 0"); +assertEq(view[367], 255, "367th value should be 255"); +assertEq(view[368], 0, "368th value should be 0"); +assertEq(view[369], 0, "369th value should be 0"); +assertEq(view[370], 0, "370th value should be 0"); +assertEq(view[371], 255, "371th value should be 255"); +assertEq(view[372], 0, "372th value should be 0"); +assertEq(view[373], 0, "373th value should be 0"); +assertEq(view[374], 0, "374th value should be 0"); +assertEq(view[375], 255, "375th value should be 255"); +assertEq(view[376], 0, "376th value should be 0"); +assertEq(view[377], 0, "377th value should be 0"); +assertEq(view[378], 0, "378th value should be 0"); +assertEq(view[379], 255, "379th value should be 255"); +assertEq(view[380], 0, "380th value should be 0"); +assertEq(view[381], 0, "381th value should be 0"); +assertEq(view[382], 0, "382th value should be 0"); +assertEq(view[383], 255, "383th value should be 255"); +assertEq(view[384], 52, "384th value should be 52"); +assertEq(view[385], 255, "385th value should be 255"); +assertEq(view[386], 203, "386th value should be 203"); +assertEq(view[387], 255, "387th value should be 255"); +assertEq(view[388], 179, "388th value should be 179"); +assertEq(view[389], 127, "389th value should be 127"); +assertEq(view[390], 76, "390th value should be 76"); +assertEq(view[391], 255, "391th value should be 255"); +assertEq(view[392], 205, "392th value should be 205"); +assertEq(view[393], 255, "393th value should be 255"); +assertEq(view[394], 50, "394th value should be 50"); +assertEq(view[395], 255, "395th value should be 255"); +assertEq(view[396], 205, "396th value should be 205"); +assertEq(view[397], 255, "397th value should be 255"); +assertEq(view[398], 50, "398th value should be 50"); +assertEq(view[399], 255, "399th value should be 255"); +assertEq(view[400], 205, "400th value should be 205"); +assertEq(view[401], 255, "401th value should be 255"); +assertEq(view[402], 50, "402th value should be 50"); +assertEq(view[403], 255, "403th value should be 255"); +assertEq(view[404], 205, "404th value should be 205"); +assertEq(view[405], 255, "405th value should be 255"); +assertEq(view[406], 50, "406th value should be 50"); +assertEq(view[407], 255, "407th value should be 255"); +assertEq(view[408], 205, "408th value should be 205"); +assertEq(view[409], 255, "409th value should be 255"); +assertEq(view[410], 50, "410th value should be 50"); +assertEq(view[411], 255, "411th value should be 255"); +assertEq(view[412], 205, "412th value should be 205"); +assertEq(view[413], 255, "413th value should be 255"); +assertEq(view[414], 50, "414th value should be 50"); +assertEq(view[415], 255, "415th value should be 255"); +assertEq(view[416], 154, "416th value should be 154"); +assertEq(view[417], 255, "417th value should be 255"); +assertEq(view[418], 101, "418th value should be 101"); +assertEq(view[419], 255, "419th value should be 255"); +assertEq(view[420], 128, "420th value should be 128"); +assertEq(view[421], 127, "421th value should be 127"); +assertEq(view[422], 127, "422th value should be 127"); +assertEq(view[423], 255, "423th value should be 255"); +assertEq(view[424], 154, "424th value should be 154"); +assertEq(view[425], 255, "425th value should be 255"); +assertEq(view[426], 101, "426th value should be 101"); +assertEq(view[427], 255, "427th value should be 255"); +assertEq(view[428], 128, "428th value should be 128"); +assertEq(view[429], 127, "429th value should be 127"); +assertEq(view[430], 127, "430th value should be 127"); +assertEq(view[431], 255, "431th value should be 255"); +assertEq(view[432], 103, "432th value should be 103"); +assertEq(view[433], 255, "433th value should be 255"); +assertEq(view[434], 152, "434th value should be 152"); +assertEq(view[435], 255, "435th value should be 255"); +assertEq(view[436], 0, "436th value should be 0"); +assertEq(view[437], 0, "437th value should be 0"); +assertEq(view[438], 0, "438th value should be 0"); +assertEq(view[439], 255, "439th value should be 255"); +assertEq(view[440], 0, "440th value should be 0"); +assertEq(view[441], 0, "441th value should be 0"); +assertEq(view[442], 0, "442th value should be 0"); +assertEq(view[443], 255, "443th value should be 255"); +assertEq(view[444], 0, "444th value should be 0"); +assertEq(view[445], 0, "445th value should be 0"); +assertEq(view[446], 0, "446th value should be 0"); +assertEq(view[447], 255, "447th value should be 255"); +assertEq(view[448], 0, "448th value should be 0"); +assertEq(view[449], 0, "449th value should be 0"); +assertEq(view[450], 0, "450th value should be 0"); +assertEq(view[451], 255, "451th value should be 255"); +assertEq(view[452], 0, "452th value should be 0"); +assertEq(view[453], 0, "453th value should be 0"); +assertEq(view[454], 0, "454th value should be 0"); +assertEq(view[455], 255, "455th value should be 255"); +assertEq(view[456], 0, "456th value should be 0"); +assertEq(view[457], 0, "457th value should be 0"); +assertEq(view[458], 0, "458th value should be 0"); +assertEq(view[459], 255, "459th value should be 255"); +assertEq(view[460], 0, "460th value should be 0"); +assertEq(view[461], 0, "461th value should be 0"); +assertEq(view[462], 0, "462th value should be 0"); +assertEq(view[463], 255, "463th value should be 255"); +assertEq(view[464], 78, "464th value should be 78"); +assertEq(view[465], 127, "465th value should be 127"); +assertEq(view[466], 178, "466th value should be 178"); +assertEq(view[467], 255, "467th value should be 255"); +assertEq(view[468], 154, "468th value should be 154"); +assertEq(view[469], 255, "469th value should be 255"); +assertEq(view[470], 101, "470th value should be 101"); +assertEq(view[471], 255, "471th value should be 255"); +assertEq(view[472], 205, "472th value should be 205"); +assertEq(view[473], 255, "473th value should be 255"); +assertEq(view[474], 50, "474th value should be 50"); +assertEq(view[475], 255, "475th value should be 255"); +assertEq(view[476], 205, "476th value should be 205"); +assertEq(view[477], 255, "477th value should be 255"); +assertEq(view[478], 50, "478th value should be 50"); +assertEq(view[479], 255, "479th value should be 255"); +assertEq(view[480], 205, "480th value should be 205"); +assertEq(view[481], 255, "481th value should be 255"); +assertEq(view[482], 50, "482th value should be 50"); +assertEq(view[483], 255, "483th value should be 255"); +assertEq(view[484], 205, "484th value should be 205"); +assertEq(view[485], 255, "485th value should be 255"); +assertEq(view[486], 50, "486th value should be 50"); +assertEq(view[487], 255, "487th value should be 255"); +assertEq(view[488], 179, "488th value should be 179"); +assertEq(view[489], 127, "489th value should be 127"); +assertEq(view[490], 76, "490th value should be 76"); +assertEq(view[491], 255, "491th value should be 255"); +assertEq(view[492], 179, "492th value should be 179"); +assertEq(view[493], 127, "493th value should be 127"); +assertEq(view[494], 76, "494th value should be 76"); +assertEq(view[495], 255, "495th value should be 255"); +assertEq(view[496], 128, "496th value should be 128"); +assertEq(view[497], 127, "497th value should be 127"); +assertEq(view[498], 127, "498th value should be 127"); +assertEq(view[499], 255, "499th value should be 255"); +assertEq(view[500], 52, "500th value should be 52"); +assertEq(view[501], 255, "501th value should be 255"); +assertEq(view[502], 203, "502th value should be 203"); +assertEq(view[503], 255, "503th value should be 255"); +assertEq(view[504], 0, "504th value should be 0"); +assertEq(view[505], 0, "505th value should be 0"); +assertEq(view[506], 0, "506th value should be 0"); +assertEq(view[507], 255, "507th value should be 255"); +assertEq(view[508], 78, "508th value should be 78"); +assertEq(view[509], 127, "509th value should be 127"); +assertEq(view[510], 178, "510th value should be 178"); +assertEq(view[511], 255, "511th value should be 255"); +assertEq(view[512], 52, "512th value should be 52"); +assertEq(view[513], 255, "513th value should be 255"); +assertEq(view[514], 203, "514th value should be 203"); +assertEq(view[515], 255, "515th value should be 255"); +assertEq(view[516], 0, "516th value should be 0"); +assertEq(view[517], 0, "517th value should be 0"); +assertEq(view[518], 0, "518th value should be 0"); +assertEq(view[519], 255, "519th value should be 255"); +assertEq(view[520], 0, "520th value should be 0"); +assertEq(view[521], 0, "521th value should be 0"); +assertEq(view[522], 0, "522th value should be 0"); +assertEq(view[523], 255, "523th value should be 255"); +assertEq(view[524], 0, "524th value should be 0"); +assertEq(view[525], 0, "525th value should be 0"); +assertEq(view[526], 0, "526th value should be 0"); +assertEq(view[527], 255, "527th value should be 255"); +assertEq(view[528], 0, "528th value should be 0"); +assertEq(view[529], 0, "529th value should be 0"); +assertEq(view[530], 0, "530th value should be 0"); +assertEq(view[531], 255, "531th value should be 255"); +assertEq(view[532], 0, "532th value should be 0"); +assertEq(view[533], 0, "533th value should be 0"); +assertEq(view[534], 0, "534th value should be 0"); +assertEq(view[535], 255, "535th value should be 255"); +assertEq(view[536], 0, "536th value should be 0"); +assertEq(view[537], 0, "537th value should be 0"); +assertEq(view[538], 0, "538th value should be 0"); +assertEq(view[539], 255, "539th value should be 255"); +assertEq(view[540], 0, "540th value should be 0"); +assertEq(view[541], 0, "541th value should be 0"); +assertEq(view[542], 0, "542th value should be 0"); +assertEq(view[543], 255, "543th value should be 255"); +assertEq(view[544], 0, "544th value should be 0"); +assertEq(view[545], 0, "545th value should be 0"); +assertEq(view[546], 0, "546th value should be 0"); +assertEq(view[547], 255, "547th value should be 255"); +assertEq(view[548], 154, "548th value should be 154"); +assertEq(view[549], 255, "549th value should be 255"); +assertEq(view[550], 101, "550th value should be 101"); +assertEq(view[551], 255, "551th value should be 255"); +assertEq(view[552], 205, "552th value should be 205"); +assertEq(view[553], 255, "553th value should be 255"); +assertEq(view[554], 50, "554th value should be 50"); +assertEq(view[555], 255, "555th value should be 255"); +assertEq(view[556], 205, "556th value should be 205"); +assertEq(view[557], 255, "557th value should be 255"); +assertEq(view[558], 50, "558th value should be 50"); +assertEq(view[559], 255, "559th value should be 255"); +assertEq(view[560], 205, "560th value should be 205"); +assertEq(view[561], 255, "561th value should be 255"); +assertEq(view[562], 50, "562th value should be 50"); +assertEq(view[563], 255, "563th value should be 255"); +assertEq(view[564], 179, "564th value should be 179"); +assertEq(view[565], 127, "565th value should be 127"); +assertEq(view[566], 76, "566th value should be 76"); +assertEq(view[567], 255, "567th value should be 255"); +assertEq(view[568], 179, "568th value should be 179"); +assertEq(view[569], 127, "569th value should be 127"); +assertEq(view[570], 76, "570th value should be 76"); +assertEq(view[571], 255, "571th value should be 255"); +assertEq(view[572], 154, "572th value should be 154"); +assertEq(view[573], 255, "573th value should be 255"); +assertEq(view[574], 101, "574th value should be 101"); +assertEq(view[575], 255, "575th value should be 255"); +assertEq(view[576], 103, "576th value should be 103"); +assertEq(view[577], 255, "577th value should be 255"); +assertEq(view[578], 152, "578th value should be 152"); +assertEq(view[579], 255, "579th value should be 255"); +assertEq(view[580], 0, "580th value should be 0"); +assertEq(view[581], 0, "581th value should be 0"); +assertEq(view[582], 0, "582th value should be 0"); +assertEq(view[583], 255, "583th value should be 255"); +assertEq(view[584], 0, "584th value should be 0"); +assertEq(view[585], 0, "585th value should be 0"); +assertEq(view[586], 0, "586th value should be 0"); +assertEq(view[587], 255, "587th value should be 255"); +assertEq(view[588], 0, "588th value should be 0"); +assertEq(view[589], 0, "589th value should be 0"); +assertEq(view[590], 0, "590th value should be 0"); +assertEq(view[591], 255, "591th value should be 255"); +assertEq(view[592], 0, "592th value should be 0"); +assertEq(view[593], 0, "593th value should be 0"); +assertEq(view[594], 0, "594th value should be 0"); +assertEq(view[595], 255, "595th value should be 255"); +assertEq(view[596], 0, "596th value should be 0"); +assertEq(view[597], 0, "597th value should be 0"); +assertEq(view[598], 0, "598th value should be 0"); +assertEq(view[599], 255, "599th value should be 255"); +assertEq(view[600], 0, "600th value should be 0"); +assertEq(view[601], 0, "601th value should be 0"); +assertEq(view[602], 0, "602th value should be 0"); +assertEq(view[603], 255, "603th value should be 255"); +assertEq(view[604], 0, "604th value should be 0"); +assertEq(view[605], 0, "605th value should be 0"); +assertEq(view[606], 0, "606th value should be 0"); +assertEq(view[607], 255, "607th value should be 255"); +assertEq(view[608], 0, "608th value should be 0"); +assertEq(view[609], 0, "609th value should be 0"); +assertEq(view[610], 0, "610th value should be 0"); +assertEq(view[611], 255, "611th value should be 255"); +assertEq(view[612], 0, "612th value should be 0"); +assertEq(view[613], 0, "613th value should be 0"); +assertEq(view[614], 0, "614th value should be 0"); +assertEq(view[615], 255, "615th value should be 255"); +assertEq(view[616], 0, "616th value should be 0"); +assertEq(view[617], 0, "617th value should be 0"); +assertEq(view[618], 0, "618th value should be 0"); +assertEq(view[619], 255, "619th value should be 255"); +assertEq(view[620], 0, "620th value should be 0"); +assertEq(view[621], 0, "621th value should be 0"); +assertEq(view[622], 0, "622th value should be 0"); +assertEq(view[623], 255, "623th value should be 255"); +assertEq(view[624], 0, "624th value should be 0"); +assertEq(view[625], 0, "625th value should be 0"); +assertEq(view[626], 0, "626th value should be 0"); +assertEq(view[627], 255, "627th value should be 255"); +assertEq(view[628], 154, "628th value should be 154"); +assertEq(view[629], 255, "629th value should be 255"); +assertEq(view[630], 101, "630th value should be 101"); +assertEq(view[631], 255, "631th value should be 255"); +assertEq(view[632], 205, "632th value should be 205"); +assertEq(view[633], 255, "633th value should be 255"); +assertEq(view[634], 50, "634th value should be 50"); +assertEq(view[635], 255, "635th value should be 255"); +assertEq(view[636], 205, "636th value should be 205"); +assertEq(view[637], 255, "637th value should be 255"); +assertEq(view[638], 50, "638th value should be 50"); +assertEq(view[639], 255, "639th value should be 255"); +assertEq(view[640], 179, "640th value should be 179"); +assertEq(view[641], 127, "641th value should be 127"); +assertEq(view[642], 76, "642th value should be 76"); +assertEq(view[643], 255, "643th value should be 255"); +assertEq(view[644], 179, "644th value should be 179"); +assertEq(view[645], 127, "645th value should be 127"); +assertEq(view[646], 76, "646th value should be 76"); +assertEq(view[647], 255, "647th value should be 255"); +assertEq(view[648], 154, "648th value should be 154"); +assertEq(view[649], 255, "649th value should be 255"); +assertEq(view[650], 101, "650th value should be 101"); +assertEq(view[651], 255, "651th value should be 255"); +assertEq(view[652], 128, "652th value should be 128"); +assertEq(view[653], 127, "653th value should be 127"); +assertEq(view[654], 127, "654th value should be 127"); +assertEq(view[655], 255, "655th value should be 255"); +assertEq(view[656], 52, "656th value should be 52"); +assertEq(view[657], 255, "657th value should be 255"); +assertEq(view[658], 203, "658th value should be 203"); +assertEq(view[659], 255, "659th value should be 255"); +assertEq(view[660], 0, "660th value should be 0"); +assertEq(view[661], 0, "661th value should be 0"); +assertEq(view[662], 0, "662th value should be 0"); +assertEq(view[663], 255, "663th value should be 255"); +assertEq(view[664], 0, "664th value should be 0"); +assertEq(view[665], 0, "665th value should be 0"); +assertEq(view[666], 0, "666th value should be 0"); +assertEq(view[667], 255, "667th value should be 255"); +assertEq(view[668], 0, "668th value should be 0"); +assertEq(view[669], 0, "669th value should be 0"); +assertEq(view[670], 0, "670th value should be 0"); +assertEq(view[671], 255, "671th value should be 255"); +assertEq(view[672], 0, "672th value should be 0"); +assertEq(view[673], 0, "673th value should be 0"); +assertEq(view[674], 0, "674th value should be 0"); +assertEq(view[675], 255, "675th value should be 255"); +assertEq(view[676], 0, "676th value should be 0"); +assertEq(view[677], 0, "677th value should be 0"); +assertEq(view[678], 0, "678th value should be 0"); +assertEq(view[679], 255, "679th value should be 255"); +assertEq(view[680], 0, "680th value should be 0"); +assertEq(view[681], 0, "681th value should be 0"); +assertEq(view[682], 0, "682th value should be 0"); +assertEq(view[683], 255, "683th value should be 255"); +assertEq(view[684], 0, "684th value should be 0"); +assertEq(view[685], 0, "685th value should be 0"); +assertEq(view[686], 0, "686th value should be 0"); +assertEq(view[687], 255, "687th value should be 255"); +assertEq(view[688], 0, "688th value should be 0"); +assertEq(view[689], 0, "689th value should be 0"); +assertEq(view[690], 0, "690th value should be 0"); +assertEq(view[691], 255, "691th value should be 255"); +assertEq(view[692], 0, "692th value should be 0"); +assertEq(view[693], 0, "693th value should be 0"); +assertEq(view[694], 0, "694th value should be 0"); +assertEq(view[695], 255, "695th value should be 255"); +assertEq(view[696], 0, "696th value should be 0"); +assertEq(view[697], 0, "697th value should be 0"); +assertEq(view[698], 0, "698th value should be 0"); +assertEq(view[699], 255, "699th value should be 255"); +assertEq(view[700], 0, "700th value should be 0"); +assertEq(view[701], 0, "701th value should be 0"); +assertEq(view[702], 0, "702th value should be 0"); +assertEq(view[703], 255, "703th value should be 255"); +assertEq(view[704], 0, "704th value should be 0"); +assertEq(view[705], 0, "705th value should be 0"); +assertEq(view[706], 0, "706th value should be 0"); +assertEq(view[707], 255, "707th value should be 255"); +assertEq(view[708], 154, "708th value should be 154"); +assertEq(view[709], 255, "709th value should be 255"); +assertEq(view[710], 101, "710th value should be 101"); +assertEq(view[711], 255, "711th value should be 255"); +assertEq(view[712], 179, "712th value should be 179"); +assertEq(view[713], 127, "713th value should be 127"); +assertEq(view[714], 76, "714th value should be 76"); +assertEq(view[715], 255, "715th value should be 255"); +assertEq(view[716], 205, "716th value should be 205"); +assertEq(view[717], 255, "717th value should be 255"); +assertEq(view[718], 50, "718th value should be 50"); +assertEq(view[719], 255, "719th value should be 255"); +assertEq(view[720], 154, "720th value should be 154"); +assertEq(view[721], 255, "721th value should be 255"); +assertEq(view[722], 101, "722th value should be 101"); +assertEq(view[723], 255, "723th value should be 255"); +assertEq(view[724], 52, "724th value should be 52"); +assertEq(view[725], 255, "725th value should be 255"); +assertEq(view[726], 203, "726th value should be 203"); +assertEq(view[727], 255, "727th value should be 255"); +assertEq(view[728], 128, "728th value should be 128"); +assertEq(view[729], 127, "729th value should be 127"); +assertEq(view[730], 127, "730th value should be 127"); +assertEq(view[731], 255, "731th value should be 255"); +assertEq(view[732], 78, "732th value should be 78"); +assertEq(view[733], 127, "733th value should be 127"); +assertEq(view[734], 178, "734th value should be 178"); +assertEq(view[735], 255, "735th value should be 255"); +assertEq(view[736], 0, "736th value should be 0"); +assertEq(view[737], 0, "737th value should be 0"); +assertEq(view[738], 0, "738th value should be 0"); +assertEq(view[739], 255, "739th value should be 255"); +assertEq(view[740], 0, "740th value should be 0"); +assertEq(view[741], 0, "741th value should be 0"); +assertEq(view[742], 0, "742th value should be 0"); +assertEq(view[743], 255, "743th value should be 255"); +assertEq(view[744], 0, "744th value should be 0"); +assertEq(view[745], 0, "745th value should be 0"); +assertEq(view[746], 0, "746th value should be 0"); +assertEq(view[747], 255, "747th value should be 255"); +assertEq(view[748], 0, "748th value should be 0"); +assertEq(view[749], 0, "749th value should be 0"); +assertEq(view[750], 0, "750th value should be 0"); +assertEq(view[751], 255, "751th value should be 255"); +assertEq(view[752], 0, "752th value should be 0"); +assertEq(view[753], 0, "753th value should be 0"); +assertEq(view[754], 0, "754th value should be 0"); +assertEq(view[755], 255, "755th value should be 255"); +assertEq(view[756], 0, "756th value should be 0"); +assertEq(view[757], 0, "757th value should be 0"); +assertEq(view[758], 0, "758th value should be 0"); +assertEq(view[759], 255, "759th value should be 255"); +assertEq(view[760], 0, "760th value should be 0"); +assertEq(view[761], 0, "761th value should be 0"); +assertEq(view[762], 0, "762th value should be 0"); +assertEq(view[763], 255, "763th value should be 255"); +assertEq(view[764], 0, "764th value should be 0"); +assertEq(view[765], 0, "765th value should be 0"); +assertEq(view[766], 0, "766th value should be 0"); +assertEq(view[767], 255, "767th value should be 255"); +assertEq(view[768], 0, "768th value should be 0"); +assertEq(view[769], 0, "769th value should be 0"); +assertEq(view[770], 0, "770th value should be 0"); +assertEq(view[771], 255, "771th value should be 255"); +assertEq(view[772], 0, "772th value should be 0"); +assertEq(view[773], 0, "773th value should be 0"); +assertEq(view[774], 0, "774th value should be 0"); +assertEq(view[775], 255, "775th value should be 255"); +assertEq(view[776], 0, "776th value should be 0"); +assertEq(view[777], 0, "777th value should be 0"); +assertEq(view[778], 0, "778th value should be 0"); +assertEq(view[779], 255, "779th value should be 255"); +assertEq(view[780], 0, "780th value should be 0"); +assertEq(view[781], 0, "781th value should be 0"); +assertEq(view[782], 0, "782th value should be 0"); +assertEq(view[783], 255, "783th value should be 255"); +assertEq(view[784], 78, "784th value should be 78"); +assertEq(view[785], 127, "785th value should be 127"); +assertEq(view[786], 178, "786th value should be 178"); +assertEq(view[787], 255, "787th value should be 255"); +assertEq(view[788], 154, "788th value should be 154"); +assertEq(view[789], 255, "789th value should be 255"); +assertEq(view[790], 101, "790th value should be 101"); +assertEq(view[791], 255, "791th value should be 255"); +assertEq(view[792], 179, "792th value should be 179"); +assertEq(view[793], 127, "793th value should be 127"); +assertEq(view[794], 76, "794th value should be 76"); +assertEq(view[795], 255, "795th value should be 255"); +assertEq(view[796], 205, "796th value should be 205"); +assertEq(view[797], 255, "797th value should be 255"); +assertEq(view[798], 50, "798th value should be 50"); +assertEq(view[799], 255, "799th value should be 255"); +assertEq(view[800], 128, "800th value should be 128"); +assertEq(view[801], 127, "801th value should be 127"); +assertEq(view[802], 127, "802th value should be 127"); +assertEq(view[803], 255, "803th value should be 255"); +assertEq(view[804], 0, "804th value should be 0"); +assertEq(view[805], 0, "805th value should be 0"); +assertEq(view[806], 0, "806th value should be 0"); +assertEq(view[807], 255, "807th value should be 255"); +assertEq(view[808], 26, "808th value should be 26"); +assertEq(view[809], 127, "809th value should be 127"); +assertEq(view[810], 229, "810th value should be 229"); +assertEq(view[811], 255, "811th value should be 255"); +assertEq(view[812], 0, "812th value should be 0"); +assertEq(view[813], 0, "813th value should be 0"); +assertEq(view[814], 0, "814th value should be 0"); +assertEq(view[815], 255, "815th value should be 255"); +assertEq(view[816], 0, "816th value should be 0"); +assertEq(view[817], 0, "817th value should be 0"); +assertEq(view[818], 0, "818th value should be 0"); +assertEq(view[819], 255, "819th value should be 255"); +assertEq(view[820], 0, "820th value should be 0"); +assertEq(view[821], 0, "821th value should be 0"); +assertEq(view[822], 0, "822th value should be 0"); +assertEq(view[823], 255, "823th value should be 255"); +assertEq(view[824], 0, "824th value should be 0"); +assertEq(view[825], 0, "825th value should be 0"); +assertEq(view[826], 0, "826th value should be 0"); +assertEq(view[827], 255, "827th value should be 255"); +assertEq(view[828], 0, "828th value should be 0"); +assertEq(view[829], 0, "829th value should be 0"); +assertEq(view[830], 0, "830th value should be 0"); +assertEq(view[831], 255, "831th value should be 255"); +assertEq(view[832], 0, "832th value should be 0"); +assertEq(view[833], 0, "833th value should be 0"); +assertEq(view[834], 0, "834th value should be 0"); +assertEq(view[835], 255, "835th value should be 255"); +assertEq(view[836], 0, "836th value should be 0"); +assertEq(view[837], 0, "837th value should be 0"); +assertEq(view[838], 0, "838th value should be 0"); +assertEq(view[839], 255, "839th value should be 255"); +assertEq(view[840], 0, "840th value should be 0"); +assertEq(view[841], 0, "841th value should be 0"); +assertEq(view[842], 0, "842th value should be 0"); +assertEq(view[843], 255, "843th value should be 255"); +assertEq(view[844], 0, "844th value should be 0"); +assertEq(view[845], 0, "845th value should be 0"); +assertEq(view[846], 0, "846th value should be 0"); +assertEq(view[847], 255, "847th value should be 255"); +assertEq(view[848], 0, "848th value should be 0"); +assertEq(view[849], 0, "849th value should be 0"); +assertEq(view[850], 0, "850th value should be 0"); +assertEq(view[851], 255, "851th value should be 255"); +assertEq(view[852], 0, "852th value should be 0"); +assertEq(view[853], 0, "853th value should be 0"); +assertEq(view[854], 0, "854th value should be 0"); +assertEq(view[855], 255, "855th value should be 255"); +assertEq(view[856], 0, "856th value should be 0"); +assertEq(view[857], 0, "857th value should be 0"); +assertEq(view[858], 0, "858th value should be 0"); +assertEq(view[859], 255, "859th value should be 255"); +assertEq(view[860], 0, "860th value should be 0"); +assertEq(view[861], 0, "861th value should be 0"); +assertEq(view[862], 0, "862th value should be 0"); +assertEq(view[863], 255, "863th value should be 255"); +assertEq(view[864], 103, "864th value should be 103"); +assertEq(view[865], 255, "865th value should be 255"); +assertEq(view[866], 152, "866th value should be 152"); +assertEq(view[867], 255, "867th value should be 255"); +assertEq(view[868], 154, "868th value should be 154"); +assertEq(view[869], 255, "869th value should be 255"); +assertEq(view[870], 101, "870th value should be 101"); +assertEq(view[871], 255, "871th value should be 255"); +assertEq(view[872], 179, "872th value should be 179"); +assertEq(view[873], 127, "873th value should be 127"); +assertEq(view[874], 76, "874th value should be 76"); +assertEq(view[875], 255, "875th value should be 255"); +assertEq(view[876], 205, "876th value should be 205"); +assertEq(view[877], 255, "877th value should be 255"); +assertEq(view[878], 50, "878th value should be 50"); +assertEq(view[879], 255, "879th value should be 255"); +assertEq(view[880], 179, "880th value should be 179"); +assertEq(view[881], 127, "881th value should be 127"); +assertEq(view[882], 76, "882th value should be 76"); +assertEq(view[883], 255, "883th value should be 255"); +assertEq(view[884], 179, "884th value should be 179"); +assertEq(view[885], 127, "885th value should be 127"); +assertEq(view[886], 76, "886th value should be 76"); +assertEq(view[887], 255, "887th value should be 255"); +assertEq(view[888], 128, "888th value should be 128"); +assertEq(view[889], 127, "889th value should be 127"); +assertEq(view[890], 127, "890th value should be 127"); +assertEq(view[891], 255, "891th value should be 255"); +assertEq(view[892], 103, "892th value should be 103"); +assertEq(view[893], 255, "893th value should be 255"); +assertEq(view[894], 152, "894th value should be 152"); +assertEq(view[895], 255, "895th value should be 255"); +assertEq(view[896], 26, "896th value should be 26"); +assertEq(view[897], 127, "897th value should be 127"); +assertEq(view[898], 229, "898th value should be 229"); +assertEq(view[899], 255, "899th value should be 255"); +assertEq(view[900], 0, "900th value should be 0"); +assertEq(view[901], 0, "901th value should be 0"); +assertEq(view[902], 0, "902th value should be 0"); +assertEq(view[903], 255, "903th value should be 255"); +assertEq(view[904], 0, "904th value should be 0"); +assertEq(view[905], 0, "905th value should be 0"); +assertEq(view[906], 0, "906th value should be 0"); +assertEq(view[907], 255, "907th value should be 255"); +assertEq(view[908], 0, "908th value should be 0"); +assertEq(view[909], 0, "909th value should be 0"); +assertEq(view[910], 0, "910th value should be 0"); +assertEq(view[911], 255, "911th value should be 255"); +assertEq(view[912], 0, "912th value should be 0"); +assertEq(view[913], 0, "913th value should be 0"); +assertEq(view[914], 0, "914th value should be 0"); +assertEq(view[915], 255, "915th value should be 255"); +assertEq(view[916], 0, "916th value should be 0"); +assertEq(view[917], 0, "917th value should be 0"); +assertEq(view[918], 0, "918th value should be 0"); +assertEq(view[919], 255, "919th value should be 255"); +assertEq(view[920], 0, "920th value should be 0"); +assertEq(view[921], 0, "921th value should be 0"); +assertEq(view[922], 0, "922th value should be 0"); +assertEq(view[923], 255, "923th value should be 255"); +assertEq(view[924], 0, "924th value should be 0"); +assertEq(view[925], 0, "925th value should be 0"); +assertEq(view[926], 0, "926th value should be 0"); +assertEq(view[927], 255, "927th value should be 255"); +assertEq(view[928], 0, "928th value should be 0"); +assertEq(view[929], 0, "929th value should be 0"); +assertEq(view[930], 0, "930th value should be 0"); +assertEq(view[931], 255, "931th value should be 255"); +assertEq(view[932], 0, "932th value should be 0"); +assertEq(view[933], 0, "933th value should be 0"); +assertEq(view[934], 0, "934th value should be 0"); +assertEq(view[935], 255, "935th value should be 255"); +assertEq(view[936], 0, "936th value should be 0"); +assertEq(view[937], 0, "937th value should be 0"); +assertEq(view[938], 0, "938th value should be 0"); +assertEq(view[939], 255, "939th value should be 255"); +assertEq(view[940], 0, "940th value should be 0"); +assertEq(view[941], 0, "941th value should be 0"); +assertEq(view[942], 0, "942th value should be 0"); +assertEq(view[943], 255, "943th value should be 255"); +assertEq(view[944], 0, "944th value should be 0"); +assertEq(view[945], 0, "945th value should be 0"); +assertEq(view[946], 0, "946th value should be 0"); +assertEq(view[947], 255, "947th value should be 255"); +assertEq(view[948], 154, "948th value should be 154"); +assertEq(view[949], 255, "949th value should be 255"); +assertEq(view[950], 101, "950th value should be 101"); +assertEq(view[951], 255, "951th value should be 255"); +assertEq(view[952], 179, "952th value should be 179"); +assertEq(view[953], 127, "953th value should be 127"); +assertEq(view[954], 76, "954th value should be 76"); +assertEq(view[955], 255, "955th value should be 255"); +assertEq(view[956], 205, "956th value should be 205"); +assertEq(view[957], 255, "957th value should be 255"); +assertEq(view[958], 50, "958th value should be 50"); +assertEq(view[959], 255, "959th value should be 255"); +assertEq(view[960], 179, "960th value should be 179"); +assertEq(view[961], 127, "961th value should be 127"); +assertEq(view[962], 76, "962th value should be 76"); +assertEq(view[963], 255, "963th value should be 255"); +assertEq(view[964], 179, "964th value should be 179"); +assertEq(view[965], 127, "965th value should be 127"); +assertEq(view[966], 76, "966th value should be 76"); +assertEq(view[967], 255, "967th value should be 255"); +assertEq(view[968], 179, "968th value should be 179"); +assertEq(view[969], 127, "969th value should be 127"); +assertEq(view[970], 76, "970th value should be 76"); +assertEq(view[971], 255, "971th value should be 255"); +assertEq(view[972], 154, "972th value should be 154"); +assertEq(view[973], 255, "973th value should be 255"); +assertEq(view[974], 101, "974th value should be 101"); +assertEq(view[975], 255, "975th value should be 255"); +assertEq(view[976], 103, "976th value should be 103"); +assertEq(view[977], 255, "977th value should be 255"); +assertEq(view[978], 152, "978th value should be 152"); +assertEq(view[979], 255, "979th value should be 255"); +assertEq(view[980], 0, "980th value should be 0"); +assertEq(view[981], 0, "981th value should be 0"); +assertEq(view[982], 0, "982th value should be 0"); +assertEq(view[983], 255, "983th value should be 255"); +assertEq(view[984], 0, "984th value should be 0"); +assertEq(view[985], 0, "985th value should be 0"); +assertEq(view[986], 0, "986th value should be 0"); +assertEq(view[987], 255, "987th value should be 255"); +assertEq(view[988], 0, "988th value should be 0"); +assertEq(view[989], 0, "989th value should be 0"); +assertEq(view[990], 0, "990th value should be 0"); +assertEq(view[991], 255, "991th value should be 255"); +assertEq(view[992], 0, "992th value should be 0"); +assertEq(view[993], 0, "993th value should be 0"); +assertEq(view[994], 0, "994th value should be 0"); +assertEq(view[995], 255, "995th value should be 255"); +assertEq(view[996], 0, "996th value should be 0"); +assertEq(view[997], 0, "997th value should be 0"); +assertEq(view[998], 0, "998th value should be 0"); +assertEq(view[999], 255, "999th value should be 255"); +assertEq(view[1000], 0, "1000th value should be 0"); +assertEq(view[1001], 0, "1001th value should be 0"); +assertEq(view[1002], 0, "1002th value should be 0"); +assertEq(view[1003], 255, "1003th value should be 255"); +assertEq(view[1004], 0, "1004th value should be 0"); +assertEq(view[1005], 0, "1005th value should be 0"); +assertEq(view[1006], 0, "1006th value should be 0"); +assertEq(view[1007], 255, "1007th value should be 255"); +assertEq(view[1008], 0, "1008th value should be 0"); +assertEq(view[1009], 0, "1009th value should be 0"); +assertEq(view[1010], 0, "1010th value should be 0"); +assertEq(view[1011], 255, "1011th value should be 255"); +assertEq(view[1012], 0, "1012th value should be 0"); +assertEq(view[1013], 0, "1013th value should be 0"); +assertEq(view[1014], 0, "1014th value should be 0"); +assertEq(view[1015], 255, "1015th value should be 255"); +assertEq(view[1016], 0, "1016th value should be 0"); +assertEq(view[1017], 0, "1017th value should be 0"); +assertEq(view[1018], 0, "1018th value should be 0"); +assertEq(view[1019], 255, "1019th value should be 255"); +assertEq(view[1020], 0, "1020th value should be 0"); +assertEq(view[1021], 0, "1021th value should be 0"); +assertEq(view[1022], 0, "1022th value should be 0"); +assertEq(view[1023], 255, "1023th value should be 255"); +assertEq(view[1024], 0, "1024th value should be 0"); +assertEq(view[1025], 0, "1025th value should be 0"); +assertEq(view[1026], 0, "1026th value should be 0"); +assertEq(view[1027], 255, "1027th value should be 255"); +assertEq(view[1028], 154, "1028th value should be 154"); +assertEq(view[1029], 255, "1029th value should be 255"); +assertEq(view[1030], 101, "1030th value should be 101"); +assertEq(view[1031], 255, "1031th value should be 255"); +assertEq(view[1032], 205, "1032th value should be 205"); +assertEq(view[1033], 255, "1033th value should be 255"); +assertEq(view[1034], 50, "1034th value should be 50"); +assertEq(view[1035], 255, "1035th value should be 255"); +assertEq(view[1036], 205, "1036th value should be 205"); +assertEq(view[1037], 255, "1037th value should be 255"); +assertEq(view[1038], 50, "1038th value should be 50"); +assertEq(view[1039], 255, "1039th value should be 255"); +assertEq(view[1040], 205, "1040th value should be 205"); +assertEq(view[1041], 255, "1041th value should be 255"); +assertEq(view[1042], 50, "1042th value should be 50"); +assertEq(view[1043], 255, "1043th value should be 255"); +assertEq(view[1044], 179, "1044th value should be 179"); +assertEq(view[1045], 127, "1045th value should be 127"); +assertEq(view[1046], 76, "1046th value should be 76"); +assertEq(view[1047], 255, "1047th value should be 255"); +assertEq(view[1048], 179, "1048th value should be 179"); +assertEq(view[1049], 127, "1049th value should be 127"); +assertEq(view[1050], 76, "1050th value should be 76"); +assertEq(view[1051], 255, "1051th value should be 255"); +assertEq(view[1052], 154, "1052th value should be 154"); +assertEq(view[1053], 255, "1053th value should be 255"); +assertEq(view[1054], 101, "1054th value should be 101"); +assertEq(view[1055], 255, "1055th value should be 255"); +assertEq(view[1056], 128, "1056th value should be 128"); +assertEq(view[1057], 127, "1057th value should be 127"); +assertEq(view[1058], 127, "1058th value should be 127"); +assertEq(view[1059], 255, "1059th value should be 255"); +assertEq(view[1060], 0, "1060th value should be 0"); +assertEq(view[1061], 0, "1061th value should be 0"); +assertEq(view[1062], 0, "1062th value should be 0"); +assertEq(view[1063], 255, "1063th value should be 255"); +assertEq(view[1064], 0, "1064th value should be 0"); +assertEq(view[1065], 0, "1065th value should be 0"); +assertEq(view[1066], 0, "1066th value should be 0"); +assertEq(view[1067], 255, "1067th value should be 255"); +assertEq(view[1068], 26, "1068th value should be 26"); +assertEq(view[1069], 127, "1069th value should be 127"); +assertEq(view[1070], 229, "1070th value should be 229"); +assertEq(view[1071], 255, "1071th value should be 255"); +assertEq(view[1072], 26, "1072th value should be 26"); +assertEq(view[1073], 127, "1073th value should be 127"); +assertEq(view[1074], 229, "1074th value should be 229"); +assertEq(view[1075], 255, "1075th value should be 255"); +assertEq(view[1076], 0, "1076th value should be 0"); +assertEq(view[1077], 0, "1077th value should be 0"); +assertEq(view[1078], 0, "1078th value should be 0"); +assertEq(view[1079], 255, "1079th value should be 255"); +assertEq(view[1080], 0, "1080th value should be 0"); +assertEq(view[1081], 0, "1081th value should be 0"); +assertEq(view[1082], 0, "1082th value should be 0"); +assertEq(view[1083], 255, "1083th value should be 255"); +assertEq(view[1084], 0, "1084th value should be 0"); +assertEq(view[1085], 0, "1085th value should be 0"); +assertEq(view[1086], 0, "1086th value should be 0"); +assertEq(view[1087], 255, "1087th value should be 255"); +assertEq(view[1088], 0, "1088th value should be 0"); +assertEq(view[1089], 0, "1089th value should be 0"); +assertEq(view[1090], 0, "1090th value should be 0"); +assertEq(view[1091], 255, "1091th value should be 255"); +assertEq(view[1092], 0, "1092th value should be 0"); +assertEq(view[1093], 0, "1093th value should be 0"); +assertEq(view[1094], 0, "1094th value should be 0"); +assertEq(view[1095], 255, "1095th value should be 255"); +assertEq(view[1096], 0, "1096th value should be 0"); +assertEq(view[1097], 0, "1097th value should be 0"); +assertEq(view[1098], 0, "1098th value should be 0"); +assertEq(view[1099], 255, "1099th value should be 255"); +assertEq(view[1100], 0, "1100th value should be 0"); +assertEq(view[1101], 0, "1101th value should be 0"); +assertEq(view[1102], 0, "1102th value should be 0"); +assertEq(view[1103], 255, "1103th value should be 255"); +assertEq(view[1104], 0, "1104th value should be 0"); +assertEq(view[1105], 0, "1105th value should be 0"); +assertEq(view[1106], 0, "1106th value should be 0"); +assertEq(view[1107], 255, "1107th value should be 255"); +assertEq(view[1108], 154, "1108th value should be 154"); +assertEq(view[1109], 255, "1109th value should be 255"); +assertEq(view[1110], 101, "1110th value should be 101"); +assertEq(view[1111], 255, "1111th value should be 255"); +assertEq(view[1112], 205, "1112th value should be 205"); +assertEq(view[1113], 255, "1113th value should be 255"); +assertEq(view[1114], 50, "1114th value should be 50"); +assertEq(view[1115], 255, "1115th value should be 255"); +assertEq(view[1116], 205, "1116th value should be 205"); +assertEq(view[1117], 255, "1117th value should be 255"); +assertEq(view[1118], 50, "1118th value should be 50"); +assertEq(view[1119], 255, "1119th value should be 255"); +assertEq(view[1120], 205, "1120th value should be 205"); +assertEq(view[1121], 255, "1121th value should be 255"); +assertEq(view[1122], 50, "1122th value should be 50"); +assertEq(view[1123], 255, "1123th value should be 255"); +assertEq(view[1124], 205, "1124th value should be 205"); +assertEq(view[1125], 255, "1125th value should be 255"); +assertEq(view[1126], 50, "1126th value should be 50"); +assertEq(view[1127], 255, "1127th value should be 255"); +assertEq(view[1128], 205, "1128th value should be 205"); +assertEq(view[1129], 255, "1129th value should be 255"); +assertEq(view[1130], 50, "1130th value should be 50"); +assertEq(view[1131], 255, "1131th value should be 255"); +assertEq(view[1132], 179, "1132th value should be 179"); +assertEq(view[1133], 127, "1133th value should be 127"); +assertEq(view[1134], 76, "1134th value should be 76"); +assertEq(view[1135], 255, "1135th value should be 255"); +assertEq(view[1136], 154, "1136th value should be 154"); +assertEq(view[1137], 255, "1137th value should be 255"); +assertEq(view[1138], 101, "1138th value should be 101"); +assertEq(view[1139], 255, "1139th value should be 255"); +assertEq(view[1140], 128, "1140th value should be 128"); +assertEq(view[1141], 127, "1141th value should be 127"); +assertEq(view[1142], 127, "1142th value should be 127"); +assertEq(view[1143], 255, "1143th value should be 255"); +assertEq(view[1144], 128, "1144th value should be 128"); +assertEq(view[1145], 127, "1145th value should be 127"); +assertEq(view[1146], 127, "1146th value should be 127"); +assertEq(view[1147], 255, "1147th value should be 255"); +assertEq(view[1148], 103, "1148th value should be 103"); +assertEq(view[1149], 255, "1149th value should be 255"); +assertEq(view[1150], 152, "1150th value should be 152"); +assertEq(view[1151], 255, "1151th value should be 255"); +assertEq(view[1152], 78, "1152th value should be 78"); +assertEq(view[1153], 127, "1153th value should be 127"); +assertEq(view[1154], 178, "1154th value should be 178"); +assertEq(view[1155], 255, "1155th value should be 255"); +assertEq(view[1156], 0, "1156th value should be 0"); +assertEq(view[1157], 0, "1157th value should be 0"); +assertEq(view[1158], 0, "1158th value should be 0"); +assertEq(view[1159], 255, "1159th value should be 255"); +assertEq(view[1160], 0, "1160th value should be 0"); +assertEq(view[1161], 0, "1161th value should be 0"); +assertEq(view[1162], 0, "1162th value should be 0"); +assertEq(view[1163], 255, "1163th value should be 255"); +assertEq(view[1164], 0, "1164th value should be 0"); +assertEq(view[1165], 0, "1165th value should be 0"); +assertEq(view[1166], 0, "1166th value should be 0"); +assertEq(view[1167], 255, "1167th value should be 255"); +assertEq(view[1168], 0, "1168th value should be 0"); +assertEq(view[1169], 0, "1169th value should be 0"); +assertEq(view[1170], 0, "1170th value should be 0"); +assertEq(view[1171], 255, "1171th value should be 255"); +assertEq(view[1172], 0, "1172th value should be 0"); +assertEq(view[1173], 0, "1173th value should be 0"); +assertEq(view[1174], 0, "1174th value should be 0"); +assertEq(view[1175], 255, "1175th value should be 255"); +assertEq(view[1176], 0, "1176th value should be 0"); +assertEq(view[1177], 0, "1177th value should be 0"); +assertEq(view[1178], 0, "1178th value should be 0"); +assertEq(view[1179], 255, "1179th value should be 255"); +assertEq(view[1180], 0, "1180th value should be 0"); +assertEq(view[1181], 0, "1181th value should be 0"); +assertEq(view[1182], 0, "1182th value should be 0"); +assertEq(view[1183], 255, "1183th value should be 255"); +assertEq(view[1184], 26, "1184th value should be 26"); +assertEq(view[1185], 127, "1185th value should be 127"); +assertEq(view[1186], 229, "1186th value should be 229"); +assertEq(view[1187], 255, "1187th value should be 255"); +assertEq(view[1188], 154, "1188th value should be 154"); +assertEq(view[1189], 255, "1189th value should be 255"); +assertEq(view[1190], 101, "1190th value should be 101"); +assertEq(view[1191], 255, "1191th value should be 255"); +assertEq(view[1192], 205, "1192th value should be 205"); +assertEq(view[1193], 255, "1193th value should be 255"); +assertEq(view[1194], 50, "1194th value should be 50"); +assertEq(view[1195], 255, "1195th value should be 255"); +assertEq(view[1196], 205, "1196th value should be 205"); +assertEq(view[1197], 255, "1197th value should be 255"); +assertEq(view[1198], 50, "1198th value should be 50"); +assertEq(view[1199], 255, "1199th value should be 255"); +assertEq(view[1200], 230, "1200th value should be 230"); +assertEq(view[1201], 127, "1201th value should be 127"); +assertEq(view[1202], 25, "1202th value should be 25"); +assertEq(view[1203], 255, "1203th value should be 255"); +assertEq(view[1204], 205, "1204th value should be 205"); +assertEq(view[1205], 255, "1205th value should be 255"); +assertEq(view[1206], 50, "1206th value should be 50"); +assertEq(view[1207], 255, "1207th value should be 255"); +assertEq(view[1208], 205, "1208th value should be 205"); +assertEq(view[1209], 255, "1209th value should be 255"); +assertEq(view[1210], 50, "1210th value should be 50"); +assertEq(view[1211], 255, "1211th value should be 255"); +assertEq(view[1212], 205, "1212th value should be 205"); +assertEq(view[1213], 255, "1213th value should be 255"); +assertEq(view[1214], 50, "1214th value should be 50"); +assertEq(view[1215], 255, "1215th value should be 255"); +assertEq(view[1216], 205, "1216th value should be 205"); +assertEq(view[1217], 255, "1217th value should be 255"); +assertEq(view[1218], 50, "1218th value should be 50"); +assertEq(view[1219], 255, "1219th value should be 255"); +assertEq(view[1220], 154, "1220th value should be 154"); +assertEq(view[1221], 255, "1221th value should be 255"); +assertEq(view[1222], 101, "1222th value should be 101"); +assertEq(view[1223], 255, "1223th value should be 255"); +assertEq(view[1224], 154, "1224th value should be 154"); +assertEq(view[1225], 255, "1225th value should be 255"); +assertEq(view[1226], 101, "1226th value should be 101"); +assertEq(view[1227], 255, "1227th value should be 255"); +assertEq(view[1228], 154, "1228th value should be 154"); +assertEq(view[1229], 255, "1229th value should be 255"); +assertEq(view[1230], 101, "1230th value should be 101"); +assertEq(view[1231], 255, "1231th value should be 255"); +assertEq(view[1232], 128, "1232th value should be 128"); +assertEq(view[1233], 127, "1233th value should be 127"); +assertEq(view[1234], 127, "1234th value should be 127"); +assertEq(view[1235], 255, "1235th value should be 255"); +assertEq(view[1236], 26, "1236th value should be 26"); +assertEq(view[1237], 127, "1237th value should be 127"); +assertEq(view[1238], 229, "1238th value should be 229"); +assertEq(view[1239], 255, "1239th value should be 255"); +assertEq(view[1240], 0, "1240th value should be 0"); +assertEq(view[1241], 0, "1241th value should be 0"); +assertEq(view[1242], 0, "1242th value should be 0"); +assertEq(view[1243], 255, "1243th value should be 255"); +assertEq(view[1244], 0, "1244th value should be 0"); +assertEq(view[1245], 0, "1245th value should be 0"); +assertEq(view[1246], 0, "1246th value should be 0"); +assertEq(view[1247], 255, "1247th value should be 255"); +assertEq(view[1248], 0, "1248th value should be 0"); +assertEq(view[1249], 0, "1249th value should be 0"); +assertEq(view[1250], 0, "1250th value should be 0"); +assertEq(view[1251], 255, "1251th value should be 255"); +assertEq(view[1252], 0, "1252th value should be 0"); +assertEq(view[1253], 0, "1253th value should be 0"); +assertEq(view[1254], 0, "1254th value should be 0"); +assertEq(view[1255], 255, "1255th value should be 255"); +assertEq(view[1256], 0, "1256th value should be 0"); +assertEq(view[1257], 0, "1257th value should be 0"); +assertEq(view[1258], 0, "1258th value should be 0"); +assertEq(view[1259], 255, "1259th value should be 255"); +assertEq(view[1260], 0, "1260th value should be 0"); +assertEq(view[1261], 0, "1261th value should be 0"); +assertEq(view[1262], 0, "1262th value should be 0"); +assertEq(view[1263], 255, "1263th value should be 255"); +assertEq(view[1264], 78, "1264th value should be 78"); +assertEq(view[1265], 127, "1265th value should be 127"); +assertEq(view[1266], 178, "1266th value should be 178"); +assertEq(view[1267], 255, "1267th value should be 255"); +assertEq(view[1268], 179, "1268th value should be 179"); +assertEq(view[1269], 127, "1269th value should be 127"); +assertEq(view[1270], 76, "1270th value should be 76"); +assertEq(view[1271], 255, "1271th value should be 255"); +assertEq(view[1272], 205, "1272th value should be 205"); +assertEq(view[1273], 255, "1273th value should be 255"); +assertEq(view[1274], 50, "1274th value should be 50"); +assertEq(view[1275], 255, "1275th value should be 255"); +assertEq(view[1276], 205, "1276th value should be 205"); +assertEq(view[1277], 255, "1277th value should be 255"); +assertEq(view[1278], 50, "1278th value should be 50"); +assertEq(view[1279], 255, "1279th value should be 255"); +assertEq(view[1280], 0, "1280th value should be 0"); +assertEq(view[1281], 0, "1281th value should be 0"); +assertEq(view[1282], 0, "1282th value should be 0"); +assertEq(view[1283], 255, "1283th value should be 255"); +assertEq(view[1284], 205, "1284th value should be 205"); +assertEq(view[1285], 255, "1285th value should be 255"); +assertEq(view[1286], 50, "1286th value should be 50"); +assertEq(view[1287], 255, "1287th value should be 255"); +assertEq(view[1288], 205, "1288th value should be 205"); +assertEq(view[1289], 255, "1289th value should be 255"); +assertEq(view[1290], 50, "1290th value should be 50"); +assertEq(view[1291], 255, "1291th value should be 255"); +assertEq(view[1292], 205, "1292th value should be 205"); +assertEq(view[1293], 255, "1293th value should be 255"); +assertEq(view[1294], 50, "1294th value should be 50"); +assertEq(view[1295], 255, "1295th value should be 255"); +assertEq(view[1296], 205, "1296th value should be 205"); +assertEq(view[1297], 255, "1297th value should be 255"); +assertEq(view[1298], 50, "1298th value should be 50"); +assertEq(view[1299], 255, "1299th value should be 255"); +assertEq(view[1300], 205, "1300th value should be 205"); +assertEq(view[1301], 255, "1301th value should be 255"); +assertEq(view[1302], 50, "1302th value should be 50"); +assertEq(view[1303], 255, "1303th value should be 255"); +assertEq(view[1304], 179, "1304th value should be 179"); +assertEq(view[1305], 127, "1305th value should be 127"); +assertEq(view[1306], 76, "1306th value should be 76"); +assertEq(view[1307], 255, "1307th value should be 255"); +assertEq(view[1308], 154, "1308th value should be 154"); +assertEq(view[1309], 255, "1309th value should be 255"); +assertEq(view[1310], 101, "1310th value should be 101"); +assertEq(view[1311], 255, "1311th value should be 255"); +assertEq(view[1312], 154, "1312th value should be 154"); +assertEq(view[1313], 255, "1313th value should be 255"); +assertEq(view[1314], 101, "1314th value should be 101"); +assertEq(view[1315], 255, "1315th value should be 255"); +assertEq(view[1316], 0, "1316th value should be 0"); +assertEq(view[1317], 0, "1317th value should be 0"); +assertEq(view[1318], 0, "1318th value should be 0"); +assertEq(view[1319], 255, "1319th value should be 255"); +assertEq(view[1320], 0, "1320th value should be 0"); +assertEq(view[1321], 0, "1321th value should be 0"); +assertEq(view[1322], 0, "1322th value should be 0"); +assertEq(view[1323], 255, "1323th value should be 255"); +assertEq(view[1324], 0, "1324th value should be 0"); +assertEq(view[1325], 0, "1325th value should be 0"); +assertEq(view[1326], 0, "1326th value should be 0"); +assertEq(view[1327], 255, "1327th value should be 255"); +assertEq(view[1328], 0, "1328th value should be 0"); +assertEq(view[1329], 0, "1329th value should be 0"); +assertEq(view[1330], 0, "1330th value should be 0"); +assertEq(view[1331], 255, "1331th value should be 255"); +assertEq(view[1332], 0, "1332th value should be 0"); +assertEq(view[1333], 0, "1333th value should be 0"); +assertEq(view[1334], 0, "1334th value should be 0"); +assertEq(view[1335], 255, "1335th value should be 255"); +assertEq(view[1336], 0, "1336th value should be 0"); +assertEq(view[1337], 0, "1337th value should be 0"); +assertEq(view[1338], 0, "1338th value should be 0"); +assertEq(view[1339], 255, "1339th value should be 255"); +assertEq(view[1340], 0, "1340th value should be 0"); +assertEq(view[1341], 0, "1341th value should be 0"); +assertEq(view[1342], 0, "1342th value should be 0"); +assertEq(view[1343], 255, "1343th value should be 255"); +assertEq(view[1344], 0, "1344th value should be 0"); +assertEq(view[1345], 0, "1345th value should be 0"); +assertEq(view[1346], 0, "1346th value should be 0"); +assertEq(view[1347], 255, "1347th value should be 255"); +assertEq(view[1348], 179, "1348th value should be 179"); +assertEq(view[1349], 127, "1349th value should be 127"); +assertEq(view[1350], 76, "1350th value should be 76"); +assertEq(view[1351], 255, "1351th value should be 255"); +assertEq(view[1352], 205, "1352th value should be 205"); +assertEq(view[1353], 255, "1353th value should be 255"); +assertEq(view[1354], 50, "1354th value should be 50"); +assertEq(view[1355], 255, "1355th value should be 255"); +assertEq(view[1356], 205, "1356th value should be 205"); +assertEq(view[1357], 255, "1357th value should be 255"); +assertEq(view[1358], 50, "1358th value should be 50"); +assertEq(view[1359], 255, "1359th value should be 255"); +assertEq(view[1360], 0, "1360th value should be 0"); +assertEq(view[1361], 0, "1361th value should be 0"); +assertEq(view[1362], 0, "1362th value should be 0"); +assertEq(view[1363], 255, "1363th value should be 255"); +assertEq(view[1364], 205, "1364th value should be 205"); +assertEq(view[1365], 255, "1365th value should be 255"); +assertEq(view[1366], 50, "1366th value should be 50"); +assertEq(view[1367], 255, "1367th value should be 255"); +assertEq(view[1368], 205, "1368th value should be 205"); +assertEq(view[1369], 255, "1369th value should be 255"); +assertEq(view[1370], 50, "1370th value should be 50"); +assertEq(view[1371], 255, "1371th value should be 255"); +assertEq(view[1372], 205, "1372th value should be 205"); +assertEq(view[1373], 255, "1373th value should be 255"); +assertEq(view[1374], 50, "1374th value should be 50"); +assertEq(view[1375], 255, "1375th value should be 255"); +assertEq(view[1376], 205, "1376th value should be 205"); +assertEq(view[1377], 255, "1377th value should be 255"); +assertEq(view[1378], 50, "1378th value should be 50"); +assertEq(view[1379], 255, "1379th value should be 255"); +assertEq(view[1380], 205, "1380th value should be 205"); +assertEq(view[1381], 255, "1381th value should be 255"); +assertEq(view[1382], 50, "1382th value should be 50"); +assertEq(view[1383], 255, "1383th value should be 255"); +assertEq(view[1384], 205, "1384th value should be 205"); +assertEq(view[1385], 255, "1385th value should be 255"); +assertEq(view[1386], 50, "1386th value should be 50"); +assertEq(view[1387], 255, "1387th value should be 255"); +assertEq(view[1388], 179, "1388th value should be 179"); +assertEq(view[1389], 127, "1389th value should be 127"); +assertEq(view[1390], 76, "1390th value should be 76"); +assertEq(view[1391], 255, "1391th value should be 255"); +assertEq(view[1392], 179, "1392th value should be 179"); +assertEq(view[1393], 127, "1393th value should be 127"); +assertEq(view[1394], 76, "1394th value should be 76"); +assertEq(view[1395], 255, "1395th value should be 255"); +assertEq(view[1396], 103, "1396th value should be 103"); +assertEq(view[1397], 255, "1397th value should be 255"); +assertEq(view[1398], 152, "1398th value should be 152"); +assertEq(view[1399], 255, "1399th value should be 255"); +assertEq(view[1400], 78, "1400th value should be 78"); +assertEq(view[1401], 127, "1401th value should be 127"); +assertEq(view[1402], 178, "1402th value should be 178"); +assertEq(view[1403], 255, "1403th value should be 255"); +assertEq(view[1404], 52, "1404th value should be 52"); +assertEq(view[1405], 255, "1405th value should be 255"); +assertEq(view[1406], 203, "1406th value should be 203"); +assertEq(view[1407], 255, "1407th value should be 255"); +assertEq(view[1408], 0, "1408th value should be 0"); +assertEq(view[1409], 0, "1409th value should be 0"); +assertEq(view[1410], 0, "1410th value should be 0"); +assertEq(view[1411], 255, "1411th value should be 255"); +assertEq(view[1412], 0, "1412th value should be 0"); +assertEq(view[1413], 0, "1413th value should be 0"); +assertEq(view[1414], 0, "1414th value should be 0"); +assertEq(view[1415], 255, "1415th value should be 255"); +assertEq(view[1416], 52, "1416th value should be 52"); +assertEq(view[1417], 255, "1417th value should be 255"); +assertEq(view[1418], 203, "1418th value should be 203"); +assertEq(view[1419], 255, "1419th value should be 255"); +assertEq(view[1420], 128, "1420th value should be 128"); +assertEq(view[1421], 127, "1421th value should be 127"); +assertEq(view[1422], 127, "1422th value should be 127"); +assertEq(view[1423], 255, "1423th value should be 255"); +assertEq(view[1424], 128, "1424th value should be 128"); +assertEq(view[1425], 127, "1425th value should be 127"); +assertEq(view[1426], 127, "1426th value should be 127"); +assertEq(view[1427], 255, "1427th value should be 255"); +assertEq(view[1428], 205, "1428th value should be 205"); +assertEq(view[1429], 255, "1429th value should be 255"); +assertEq(view[1430], 50, "1430th value should be 50"); +assertEq(view[1431], 255, "1431th value should be 255"); +assertEq(view[1432], 205, "1432th value should be 205"); +assertEq(view[1433], 255, "1433th value should be 255"); +assertEq(view[1434], 50, "1434th value should be 50"); +assertEq(view[1435], 255, "1435th value should be 255"); +assertEq(view[1436], 230, "1436th value should be 230"); +assertEq(view[1437], 127, "1437th value should be 127"); +assertEq(view[1438], 25, "1438th value should be 25"); +assertEq(view[1439], 255, "1439th value should be 255"); +assertEq(view[1440], 0, "1440th value should be 0"); +assertEq(view[1441], 0, "1441th value should be 0"); +assertEq(view[1442], 0, "1442th value should be 0"); +assertEq(view[1443], 255, "1443th value should be 255"); +assertEq(view[1444], 230, "1444th value should be 230"); +assertEq(view[1445], 127, "1445th value should be 127"); +assertEq(view[1446], 25, "1446th value should be 25"); +assertEq(view[1447], 255, "1447th value should be 255"); +assertEq(view[1448], 205, "1448th value should be 205"); +assertEq(view[1449], 255, "1449th value should be 255"); +assertEq(view[1450], 50, "1450th value should be 50"); +assertEq(view[1451], 255, "1451th value should be 255"); +assertEq(view[1452], 205, "1452th value should be 205"); +assertEq(view[1453], 255, "1453th value should be 255"); +assertEq(view[1454], 50, "1454th value should be 50"); +assertEq(view[1455], 255, "1455th value should be 255"); +assertEq(view[1456], 205, "1456th value should be 205"); +assertEq(view[1457], 255, "1457th value should be 255"); +assertEq(view[1458], 50, "1458th value should be 50"); +assertEq(view[1459], 255, "1459th value should be 255"); +assertEq(view[1460], 205, "1460th value should be 205"); +assertEq(view[1461], 255, "1461th value should be 255"); +assertEq(view[1462], 50, "1462th value should be 50"); +assertEq(view[1463], 255, "1463th value should be 255"); +assertEq(view[1464], 205, "1464th value should be 205"); +assertEq(view[1465], 255, "1465th value should be 255"); +assertEq(view[1466], 50, "1466th value should be 50"); +assertEq(view[1467], 255, "1467th value should be 255"); +assertEq(view[1468], 179, "1468th value should be 179"); +assertEq(view[1469], 127, "1469th value should be 127"); +assertEq(view[1470], 76, "1470th value should be 76"); +assertEq(view[1471], 255, "1471th value should be 255"); +assertEq(view[1472], 179, "1472th value should be 179"); +assertEq(view[1473], 127, "1473th value should be 127"); +assertEq(view[1474], 76, "1474th value should be 76"); +assertEq(view[1475], 255, "1475th value should be 255"); +assertEq(view[1476], 179, "1476th value should be 179"); +assertEq(view[1477], 127, "1477th value should be 127"); +assertEq(view[1478], 76, "1478th value should be 76"); +assertEq(view[1479], 255, "1479th value should be 255"); +assertEq(view[1480], 128, "1480th value should be 128"); +assertEq(view[1481], 127, "1481th value should be 127"); +assertEq(view[1482], 127, "1482th value should be 127"); +assertEq(view[1483], 255, "1483th value should be 255"); +assertEq(view[1484], 103, "1484th value should be 103"); +assertEq(view[1485], 255, "1485th value should be 255"); +assertEq(view[1486], 152, "1486th value should be 152"); +assertEq(view[1487], 255, "1487th value should be 255"); +assertEq(view[1488], 0, "1488th value should be 0"); +assertEq(view[1489], 0, "1489th value should be 0"); +assertEq(view[1490], 0, "1490th value should be 0"); +assertEq(view[1491], 255, "1491th value should be 255"); +assertEq(view[1492], 0, "1492th value should be 0"); +assertEq(view[1493], 0, "1493th value should be 0"); +assertEq(view[1494], 0, "1494th value should be 0"); +assertEq(view[1495], 255, "1495th value should be 255"); +assertEq(view[1496], 128, "1496th value should be 128"); +assertEq(view[1497], 127, "1497th value should be 127"); +assertEq(view[1498], 127, "1498th value should be 127"); +assertEq(view[1499], 255, "1499th value should be 255"); +assertEq(view[1500], 154, "1500th value should be 154"); +assertEq(view[1501], 255, "1501th value should be 255"); +assertEq(view[1502], 101, "1502th value should be 101"); +assertEq(view[1503], 255, "1503th value should be 255"); +assertEq(view[1504], 179, "1504th value should be 179"); +assertEq(view[1505], 127, "1505th value should be 127"); +assertEq(view[1506], 76, "1506th value should be 76"); +assertEq(view[1507], 255, "1507th value should be 255"); +assertEq(view[1508], 205, "1508th value should be 205"); +assertEq(view[1509], 255, "1509th value should be 255"); +assertEq(view[1510], 50, "1510th value should be 50"); +assertEq(view[1511], 255, "1511th value should be 255"); +assertEq(view[1512], 205, "1512th value should be 205"); +assertEq(view[1513], 255, "1513th value should be 255"); +assertEq(view[1514], 50, "1514th value should be 50"); +assertEq(view[1515], 255, "1515th value should be 255"); +assertEq(view[1516], 230, "1516th value should be 230"); +assertEq(view[1517], 127, "1517th value should be 127"); +assertEq(view[1518], 25, "1518th value should be 25"); +assertEq(view[1519], 255, "1519th value should be 255"); +assertEq(view[1520], 0, "1520th value should be 0"); +assertEq(view[1521], 0, "1521th value should be 0"); +assertEq(view[1522], 0, "1522th value should be 0"); +assertEq(view[1523], 255, "1523th value should be 255"); +assertEq(view[1524], 230, "1524th value should be 230"); +assertEq(view[1525], 127, "1525th value should be 127"); +assertEq(view[1526], 25, "1526th value should be 25"); +assertEq(view[1527], 255, "1527th value should be 255"); +assertEq(view[1528], 230, "1528th value should be 230"); +assertEq(view[1529], 127, "1529th value should be 127"); +assertEq(view[1530], 25, "1530th value should be 25"); +assertEq(view[1531], 255, "1531th value should be 255"); +assertEq(view[1532], 205, "1532th value should be 205"); +assertEq(view[1533], 255, "1533th value should be 255"); +assertEq(view[1534], 50, "1534th value should be 50"); +assertEq(view[1535], 255, "1535th value should be 255"); +assertEq(view[1536], 205, "1536th value should be 205"); +assertEq(view[1537], 255, "1537th value should be 255"); +assertEq(view[1538], 50, "1538th value should be 50"); +assertEq(view[1539], 255, "1539th value should be 255"); +assertEq(view[1540], 205, "1540th value should be 205"); +assertEq(view[1541], 255, "1541th value should be 255"); +assertEq(view[1542], 50, "1542th value should be 50"); +assertEq(view[1543], 255, "1543th value should be 255"); +assertEq(view[1544], 205, "1544th value should be 205"); +assertEq(view[1545], 255, "1545th value should be 255"); +assertEq(view[1546], 50, "1546th value should be 50"); +assertEq(view[1547], 255, "1547th value should be 255"); +assertEq(view[1548], 205, "1548th value should be 205"); +assertEq(view[1549], 255, "1549th value should be 255"); +assertEq(view[1550], 50, "1550th value should be 50"); +assertEq(view[1551], 255, "1551th value should be 255"); +assertEq(view[1552], 179, "1552th value should be 179"); +assertEq(view[1553], 127, "1553th value should be 127"); +assertEq(view[1554], 76, "1554th value should be 76"); +assertEq(view[1555], 255, "1555th value should be 255"); +assertEq(view[1556], 179, "1556th value should be 179"); +assertEq(view[1557], 127, "1557th value should be 127"); +assertEq(view[1558], 76, "1558th value should be 76"); +assertEq(view[1559], 255, "1559th value should be 255"); +assertEq(view[1560], 179, "1560th value should be 179"); +assertEq(view[1561], 127, "1561th value should be 127"); +assertEq(view[1562], 76, "1562th value should be 76"); +assertEq(view[1563], 255, "1563th value should be 255"); +assertEq(view[1564], 154, "1564th value should be 154"); +assertEq(view[1565], 255, "1565th value should be 255"); +assertEq(view[1566], 101, "1566th value should be 101"); +assertEq(view[1567], 255, "1567th value should be 255"); +assertEq(view[1568], 26, "1568th value should be 26"); +assertEq(view[1569], 127, "1569th value should be 127"); +assertEq(view[1570], 229, "1570th value should be 229"); +assertEq(view[1571], 255, "1571th value should be 255"); +assertEq(view[1572], 0, "1572th value should be 0"); +assertEq(view[1573], 0, "1573th value should be 0"); +assertEq(view[1574], 0, "1574th value should be 0"); +assertEq(view[1575], 255, "1575th value should be 255"); +assertEq(view[1576], 154, "1576th value should be 154"); +assertEq(view[1577], 255, "1577th value should be 255"); +assertEq(view[1578], 101, "1578th value should be 101"); +assertEq(view[1579], 255, "1579th value should be 255"); +assertEq(view[1580], 179, "1580th value should be 179"); +assertEq(view[1581], 127, "1581th value should be 127"); +assertEq(view[1582], 76, "1582th value should be 76"); +assertEq(view[1583], 255, "1583th value should be 255"); +assertEq(view[1584], 205, "1584th value should be 205"); +assertEq(view[1585], 255, "1585th value should be 255"); +assertEq(view[1586], 50, "1586th value should be 50"); +assertEq(view[1587], 255, "1587th value should be 255"); +assertEq(view[1588], 205, "1588th value should be 205"); +assertEq(view[1589], 255, "1589th value should be 255"); +assertEq(view[1590], 50, "1590th value should be 50"); +assertEq(view[1591], 255, "1591th value should be 255"); +assertEq(view[1592], 230, "1592th value should be 230"); +assertEq(view[1593], 127, "1593th value should be 127"); +assertEq(view[1594], 25, "1594th value should be 25"); +assertEq(view[1595], 255, "1595th value should be 255"); +assertEq(view[1596], 230, "1596th value should be 230"); +assertEq(view[1597], 127, "1597th value should be 127"); +assertEq(view[1598], 25, "1598th value should be 25"); +assertEq(view[1599], 255, "1599th value should be 255"); + +// Code used to generate the assertEq list above. +function generateAssertList() { + function template(i, x) { + return 'assertEq(view[' + i + '], ' + x + ', "' + i + 'th value should be ' + x + '");\n'; + } + var buf = '' + for (var i = 0; i < LIMIT_SHOW; i++) + buf += template(i, view[i]); + print(buf); +} +//generateAssertList(); diff --git a/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js b/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js index be51138abd1c..f5e2319a586e 100644 --- a/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js +++ b/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js @@ -13,6 +13,17 @@ asmLink(asmJS, this, null, asmJSBuf); var wasmMem = wasmEvalText('(module (memory 1 1) (export "mem" memory))').exports.mem; assertAsmLinkFail(asmJS, this, null, wasmMem.buffer); +if (!getBuildConfiguration().x64 && isSimdAvailable() && this["SIMD"]) { + var simdJS = asmCompile('stdlib', 'ffis', 'buf', USE_ASM + 'var i32 = new stdlib.Int32Array(buf); var i32x4 = stdlib.SIMD.Int32x4; return {}'); + assertAsmLinkFail(simdJS, this, null, asmJSBuf); + assertAsmLinkFail(simdJS, this, null, wasmMem.buffer); + + var simdJSBuf = new ArrayBuffer(BUF_MIN); + asmLink(simdJS, this, null, simdJSBuf); + asmLink(simdJS, this, null, simdJSBuf); // multiple SIMD.js instantiations succeed + assertAsmLinkFail(asmJS, this, null, simdJSBuf); // but not asm.js +} + setJitCompilerOption('asmjs.atomics.enable', 1); var sharedAsmJS = asmCompile('stdlib', 'ffis', 'buf', diff --git a/js/src/jit-test/tests/asm.js/testBug1099216.js b/js/src/jit-test/tests/asm.js/testBug1099216.js new file mode 100644 index 000000000000..3514f307d5a7 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testBug1099216.js @@ -0,0 +1,61 @@ +if (typeof SIMD === 'undefined' || !isSimdAvailable()) { + print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +(function(global) { + "use asm"; + var frd = global.Math.fround; + var fx4 = global.SIMD.Float32x4; + var fc4 = fx4.check; + var fsp = fx4.splat; + function s(){} + function d(x){x=fc4(x);} + function e() { + var x = frd(0); + x = frd(x / x); + s(); + d(fsp(x)); + } + return e; +})(this)(); + +(function(m) { + "use asm" + var k = m.SIMD.Bool32x4 + var g = m.SIMD.Int32x4 + var gc = g.check; + var h = g.select + function f() { + var x = k(0, 0, 0, 0) + var y = g(1, 2, 3, 4) + return gc(h(x, y, y)) + } + return f; +})(this)(); + +t = (function(global) { + "use asm" + var toF = global.Math.fround + var f4 = global.SIMD.Float32x4 + var f4c = f4.check + function p(x, y, width, value, max_iterations) { + x = x | 0 + y = y | 0 + width = width | 0 + value = value | 0 + max_iterations = max_iterations | 0 + } + function m(xf, yf, yd, max_iterations) { + xf = toF(xf) + yf = toF(yf) + yd = toF(yd) + max_iterations = max_iterations | 0 + var _ = f4(0, 0, 0, 0), c_im4 = f4(0, 0, 0, 0) + c_im4 = f4(yf, yd, yd, yf) + return f4c(c_im4); + } + return {p:p,m:m}; +})(this) +t.p(); +t.m(); diff --git a/js/src/jit-test/tests/asm.js/testJumpRange.js b/js/src/jit-test/tests/asm.js/testJumpRange.js index 10e37752de9c..5a1d31bd0971 100644 --- a/js/src/jit-test/tests/asm.js/testJumpRange.js +++ b/js/src/jit-test/tests/asm.js/testJumpRange.js @@ -18,6 +18,26 @@ for (let threshold of [0, 50, 100, 5000, -1]) { return h `)()(), 45); + if (isSimdAvailable() && this.SIMD) { + var buf = new ArrayBuffer(BUF_MIN); + new Int32Array(buf)[0] = 10; + new Float32Array(buf)[1] = 42; + assertEq(asmCompile('stdlib', 'ffis', 'buf', + USE_ASM + ` + var H = new stdlib.Uint8Array(buf); + var i4 = stdlib.SIMD.Int32x4; + var f4 = stdlib.SIMD.Float32x4; + var i4load = i4.load; + var f4load = f4.load; + var toi4 = i4.fromFloat32x4; + var i4ext = i4.extractLane; + function f(i) { i=i|0; return i4ext(i4load(H, i), 0)|0 } + function g(i) { i=i|0; return (i4ext(toi4(f4load(H, i)),1) + (f(i)|0))|0 } + function h(i) { i=i|0; return g(i)|0 } + return h + `)(this, null, buf)(0), 52); + } + enableGeckoProfiling(); asmLink(asmCompile(USE_ASM + 'function f() {} function g() { f() } function h() { g() } return h'))(); disableGeckoProfiling(); diff --git a/js/src/jit-test/tests/asm.js/testProfiling.js b/js/src/jit-test/tests/asm.js/testProfiling.js index 502c13c043a5..3bdeb4fac075 100644 --- a/js/src/jit-test/tests/asm.js/testProfiling.js +++ b/js/src/jit-test/tests/asm.js/testProfiling.js @@ -210,6 +210,20 @@ if (jitOptions['baseline.enable']) { assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>"); } + +if (isSimdAvailable() && typeof SIMD !== 'undefined') { + // SIMD out-of-bounds exit + var buf = new ArrayBuffer(0x10000); + var f = asmLink(asmCompile('g','ffi','buf', USE_ASM + 'var f4=g.SIMD.float32x4; var f4l=f4.load; var u8=new g.Uint8Array(buf); function f(i) { i=i|0; return f4l(u8, 0xFFFF + i | 0); } return f'), this, {}, buf); + enableSingleStepProfiling(); + assertThrowsInstanceOf(() => f(4), RangeError); + var stacks = disableSingleStepProfiling(); + // TODO check that expected is actually the correctly expected string, when + // SIMD is implemented on ARM. + assertStackContainsSeq(stacks, ">,f,>,inline stub,f,>"); +} + + // Thunks setJitCompilerOption("jump-threshold", 0); var h = asmLink(asmCompile(USE_ASM + 'function f() {} function g() { f() } function h() { g() } return h')); diff --git a/js/src/jit-test/tests/asm.js/testSIMD-16x8.js b/js/src/jit-test/tests/asm.js/testSIMD-16x8.js new file mode 100644 index 000000000000..7d0d6657ff26 --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD-16x8.js @@ -0,0 +1,525 @@ +load(libdir + "asm.js"); +load(libdir + "simd.js"); +load(libdir + "asserts.js"); + +// Set to true to see more JS debugging spew. +const DEBUG = false; + +if (!isSimdAvailable()) { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +// Tests for 16x8 SIMD types: Int16x8, Uint16x8, Bool16x8. + +const I16x8 = 'var i16x8 = glob.SIMD.Int16x8;' +const I16x8CHK = 'var i16x8chk = i16x8.check;' +const I16x8EXT = 'var i16x8ext = i16x8.extractLane;' +const I16x8REP = 'var i16x8rep = i16x8.replaceLane;' +const I16x8U16x8 = 'var i16x8u16x8 = i16x8.fromUint16x8Bits;' + +const U16x8 = 'var u16x8 = glob.SIMD.Uint16x8;' +const U16x8CHK = 'var u16x8chk = u16x8.check;' +const U16x8EXT = 'var u16x8ext = u16x8.extractLane;' +const U16x8REP = 'var u16x8rep = u16x8.replaceLane;' +const U16x8I16x8 = 'var u16x8i16x8 = u16x8.fromInt16x8Bits;' + +const B16x8 = 'var b16x8 = glob.SIMD.Bool16x8;' +const B16x8CHK = 'var b16x8chk = b16x8.check;' +const B16x8EXT = 'var b16x8ext = b16x8.extractLane;' +const B16x8REP = 'var b16x8rep = b16x8.replaceLane;' + +const INT16_MAX = 0x7fff +const INT16_MIN = -0x10000 +const UINT16_MAX = 0xffff + +// Linking +assertEq(asmLink(asmCompile('glob', USE_ASM + I16x8 + "function f() {} return f"), {SIMD:{Int16x8: SIMD.Int16x8}})(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + U16x8 + "function f() {} return f"), {SIMD:{Uint16x8: SIMD.Uint16x8}})(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + B16x8 + "function f() {} return f"), {SIMD:{Bool16x8: SIMD.Bool16x8}})(), undefined); + +// Local variable of Int16x8 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Int16x8(1,2,3,4,5,6,7,8);} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8;} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8();} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4,5,6,7,8.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4,5,6,7,8,9);} return f"); +assertAsmTypeFail('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4,5,6,7,8|0);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4,5,6,7,8);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + I16x8 + "function f() {var x=i16x8(1,2,3,4,5,6,7," + (INT16_MAX + 1) + ");} return f"), this)(), undefined); + +// Local variable of Uint16x8 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Uint16x8(1,2,3,4,5,6,7,8);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8;} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8();} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4,5,6,7,8.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4,5,6,7,8,9);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4,5,6,7,8|0);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4,5,6,7,8);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + U16x8 + "function f() {var x=u16x8(1,2,3,4,5,6,7," + (UINT16_MAX + 1) + ");} return f"), this)(), undefined); + +// Local variable of Bool16x8 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Bool16x8(1,0,0,0, 0,0,0,0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8;} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8();} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1,0,0,0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1,0,0,0, 0,0,0,1.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1,0,0,0, 0,0,0,0|0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1,0,0,0, 0,0,0,0, 1);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + B16x8 + "function f() {var x=b16x8(1,0,0,0, 0,-1,-2,0);} return f"), this)(), undefined); + +// Global variable of Int16x8 type. +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + I16x8 + I16x8CHK + "var g=i16x8chk(ffi.g); function f() { return i16x8chk(g); } return f"), this, + {g: SIMD.Int16x8(1,2,3,4,5,6,7,8)})(), [1,2,3,4,5,6,7,8]); +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + I16x8 + I16x8CHK + "var g=i16x8chk(ffi.g); function f() { g=i16x8(5,6,7,8,9,10,11,12); return i16x8chk(g); } return f"), this, + {g: SIMD.Int16x8(1,2,3,4,5,6,7,8)})(), [5,6,7,8,9,10,11,12]); + +// Global variable of Bool16x8 type. +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + B16x8 + B16x8CHK + "var g=b16x8chk(ffi.g); function f() { return b16x8chk(g); } return f"), this, + {g: SIMD.Bool16x8(1,1,0,1,0,0,1,0)})(), [true,true,false,true,false,false,true,false]); +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + B16x8 + B16x8CHK + "var g=b16x8chk(ffi.g); function f() { g=b16x8(1,1,0,1,0,1,1,1); return b16x8chk(g); } return f"), this, + {g: SIMD.Bool16x8(1,1,0,1,0,0,1,0)})(), [true,true,false,true,false,true,true,true]); + +// Unsigned SIMD globals are not allowed. +assertAsmTypeFail('glob', 'ffi', USE_ASM + U16x8 + U16x8CHK + "var g=u16x8chk(ffi.g); function f() { } return f"); + +// Only signed Int16x8 allowed as return value. +assertEqVecArr(asmLink(asmCompile('glob', USE_ASM + I16x8 + "function f() {return i16x8(1,2,3,4,5,6,7,8);} return f"), this)(), + [1, 2, 3, 4, 5, 6, 7, 8]); +assertEqVecArr(asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + "function f() {return i16x8chk(i16x8(1,2,3,32771,5,6,7,8));} return f"), this)(), + [1, 2, 3, -32765, 5, 6, 7, 8]); +assertAsmTypeFail('glob', USE_ASM + U16x8 + "function f() {return u16x8(1,2,3,4,5,6,7,8);} return f"); +assertAsmTypeFail('glob', USE_ASM + U16x8 + U16x8CHK + "function f() {return u16x8chk(u16x8(1,2,3,4,5,6,7,8));} return f"); + +// Test splat. +function splat(x) { + let r = [] + for (let i = 0; i < 8; i++) + r.push(x); + return r +} + +splatB = asmLink(asmCompile('glob', USE_ASM + B16x8 + + 'var splat = b16x8.splat;' + + 'function f(x) { x = x|0; return splat(x); } return f'), this); +assertEqVecArr(splatB(true), splat(true)); +assertEqVecArr(splatB(false), splat(false)); + + +splatB0 = asmLink(asmCompile('glob', USE_ASM + B16x8 + + 'var splat = b16x8.splat;' + + 'function f() { var x = 0; return splat(x); } return f'), this); +assertEqVecArr(splatB0(), splat(false)); +splatB1 = asmLink(asmCompile('glob', USE_ASM + B16x8 + + 'var splat = b16x8.splat;' + + 'function f() { var x = 1; return splat(x); } return f'), this); +assertEqVecArr(splatB1(), splat(true)); + +splatI = asmLink(asmCompile('glob', USE_ASM + I16x8 + + 'var splat = i16x8.splat;' + + 'function f(x) { x = x|0; return splat(x); } return f'), this); +for (let x of [0, 1, -1, 0x12345, 0x1234, -1000, -1000000]) { + assertEqVecArr(splatI(x), splat(x << 16 >> 16)); +} + +splatIc = asmLink(asmCompile('glob', USE_ASM + I16x8 + + 'var splat = i16x8.splat;' + + 'function f() { var x = 100; return splat(x); } return f'), this); +assertEqVecArr(splatIc(), splat(100)) + +splatU = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8U16x8 + + 'var splat = u16x8.splat;' + + 'function f(x) { x = x|0; return i16x8u16x8(splat(x)); } return f'), this); +for (let x of [0, 1, -1, 0x12345, 0x1234, -1000, -1000000]) { + assertEqVecArr(SIMD.Uint16x8.fromInt16x8Bits(splatI(x)), splat(x << 16 >>> 16)); +} + +splatUc = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8U16x8 + + 'var splat = u16x8.splat;' + + 'function f() { var x = 200; return i16x8u16x8(splat(x)); } return f'), this); +assertEqVecArr(SIMD.Uint16x8.fromInt16x8Bits(splatUc()), splat(200)) + + +// Test extractLane. +// +// The lane index must be a literal int, and we generate different code for +// different lanes. +function extractI(a, i) { + return asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8EXT + + `function f() {var x=i16x8(${a.join(',')}); return i16x8ext(x, ${i})|0; } return f`), this)(); +} +a = [-1,2,-3,4,-5,6,-7,-8]; +for (var i = 0; i < 8; i++) + assertEq(extractI(a, i), a[i]); +a = a.map(x => -x); +for (var i = 0; i < 8; i++) + assertEq(extractI(a, i), a[i]); + +function extractU(a, i) { + return asmLink(asmCompile('glob', USE_ASM + U16x8 + U16x8EXT + + `function f() {var x=u16x8(${a.join(',')}); return u16x8ext(x, ${i})|0; } return f`), this)(); +} +a = [1,255,12,13,14,150,200,3]; +for (var i = 0; i < 8; i++) + assertEq(extractU(a, i), a[i]); +a = a.map(x => UINT16_MAX-x); +for (var i = 0; i < 8; i++) + assertEq(extractU(a, i), a[i]); + +function extractB(a, i) { + return asmLink(asmCompile('glob', USE_ASM + B16x8 + B16x8EXT + + `function f() {var x=b16x8(${a.join(',')}); return b16x8ext(x, ${i})|0; } return f`), this)(); +} +a = [1,1,0,1, 1,0,0,0]; +for (var i = 0; i < 8; i++) + assertEq(extractB(a, i), a[i]); +a = a.map(x => 1-x); +for (var i = 0; i < 8; i++) + assertEq(extractB(a, i), a[i]); + +// Test replaceLane. +function replaceI(a, i) { + return asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8REP + + `function f(v) {v=v|0; var x=i16x8(${a.join(',')}); return i16x8rep(x,${i},v); } return f`), this); +} +a = [-1,2,-3,4,-5,6,-7,-9]; +for (var i = 0; i < 8; i++) { + var f = replaceI(a, i); + var b = a.slice(0); + b[i] = -20; + assertEqVecArr(f(-20), b); +} + +function replaceU(a, i) { + return asmLink(asmCompile('glob', USE_ASM + U16x8 + U16x8REP + I16x8 + I16x8U16x8 + + `function f(v) {v=v|0; var x=u16x8(${a.join(',')}); return i16x8u16x8(u16x8rep(x,${i},v)); } return f`), this); +} +a = [65000-1,2,65000-3,4,65000-5,6,65000-7,65000-9]; +for (var i = 0; i < 8; i++) { + var rawf = replaceU(a, i); + var f = x => SIMD.Uint16x8.fromInt16x8Bits(rawf(x)) + var b = a.slice(0); + b[i] = 1000; + assertEqVecArr(f(1000), b); +} + +function replaceB(a, i) { + return asmLink(asmCompile('glob', USE_ASM + B16x8 + B16x8REP + + `function f(v) {v=v|0; var x=b16x8(${a.join(',')}); return b16x8rep(x,${i},v); } return f`), this); +} +a = [1,1,0,1,1,0,0,0]; +for (var i = 0; i < 8; i++) { + var f = replaceB(a, i); + var b = a.slice(0); + let v = 1 - a[i]; + b[i] = v; + assertEqVecArr(f(v), b.map(x => !!x)); +} + + +// Test select. +selectI = asmLink(asmCompile('glob', USE_ASM + I16x8 + B16x8 + B16x8CHK + + 'var select = i16x8.select;' + + 'var a = i16x8(-1,2,-3,4,-5, 6,-7, 8);' + + 'var b = i16x8( 5,6, 7,8, 9,10,11,12);' + + 'function f(x) { x = b16x8chk(x); return select(x, a, b); } return f'), this); +assertEqVecArr(selectI(SIMD.Bool16x8( 0,0, 1,0, 1,1, 1, 0)), + [ 5,6,-3,8,-5,6,-7,12]); + +selectU = asmLink(asmCompile('glob', USE_ASM + I16x8 + B16x8 + B16x8CHK + U16x8 + I16x8U16x8 + U16x8I16x8 + + 'var select = u16x8.select;' + + 'var a = i16x8(-1,2,-3,4,-5, 6,-7, 8);' + + 'var b = i16x8( 5,6, 7,8, 9,10,11,12);' + + 'function f(x) { x = b16x8chk(x); return i16x8u16x8(select(x, u16x8i16x8(a), u16x8i16x8(b))); } return f'), this); +assertEqVecArr(selectU(SIMD.Bool16x8( 0,0, 1,0, 1,1, 1, 0)), + [ 5,6,-3,8,-5,6,-7,12]); + +// Test swizzle. +function swizzle(vec, lanes) { + let r = []; + for (let i = 0; i < 8; i++) + r.push(vec[lanes[i]]); + return r; +} + +function swizzleI(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + 'var swz = i16x8.swizzle;' + + `function f(a) { a = i16x8chk(a); return swz(a, ${lanes.join()}); } return f`), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(asm(v1), swizzle(a1, lanes)); + assertEqVecArr(asm(v2), swizzle(a2, lanes)); +} + +swizzleI([3, 4, 7, 1, 4, 3, 1, 2]); +swizzleI([0, 0, 0, 0, 0, 0, 0, 0]); +swizzleI([7, 7, 7, 7, 7, 7, 7, 7]); + +function swizzleU(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + U16x8 + U16x8I16x8 + I16x8U16x8 + + 'var swz = u16x8.swizzle;' + + `function f(a) { a = i16x8chk(a); return i16x8u16x8(swz(u16x8i16x8(a), ${lanes.join()})); } return f`), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(asm(v1), swizzle(a1, lanes)); + assertEqVecArr(asm(v2), swizzle(a2, lanes)); +} + +swizzleU([3, 4, 7, 1, 4, 3, 1, 2]); +swizzleU([0, 0, 0, 0, 0, 0, 0, 0]); +swizzleU([7, 7, 7, 7, 7, 7, 7, 7]); + +// Out-of-range lane indexes. +assertAsmTypeFail('glob', USE_ASM + I16x8 + 'var swz = i16x8.swizzle; ' + + 'function f() { var x=i16x8(0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8); } return f'); +assertAsmTypeFail('glob', USE_ASM + U16x8 + 'var swz = u16x8.swizzle; ' + + 'function f() { var x=u16x8(0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8); } return f'); +// Missing lane indexes. +assertAsmTypeFail('glob', USE_ASM + I16x8 + 'var swz = i16x8.swizzle; ' + + 'function f() { var x=i16x8(0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7); } return f'); +assertAsmTypeFail('glob', USE_ASM + U16x8 + 'var swz = u16x8.swizzle; ' + + 'function f() { var x=u16x8(0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7); } return f'); + + +// Test shuffle. +function shuffle(vec1, vec2, lanes) { + let r = []; + let vec = vec1.concat(vec2) + for (let i = 0; i < 8; i++) + r.push(vec[lanes[i]]); + return r; +} + +function shuffleI(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + 'var shuf = i16x8.shuffle;' + + `function f(a1, a2) { a1 = i16x8chk(a1); a2 = i16x8chk(a2); return shuf(a1, a2, ${lanes.join()}); } return f`), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(asm(v1, v2), shuffle(a1, a2, lanes)); +} + +function shuffleU(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + U16x8 + U16x8I16x8 + I16x8U16x8 + + 'var shuf = u16x8.shuffle;' + + 'function f(a1, a2) { a1 = i16x8chk(a1); a2 = i16x8chk(a2); ' + + `return i16x8u16x8(shuf(u16x8i16x8(a1), u16x8i16x8(a2), ${lanes.join()})); } return f`), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(asm(v1, v2), shuffle(a1, a2, lanes)); +} + +shuffleI([0, 0, 0, 0, 0, 0, 0, 0]) +shuffleI([15, 15, 15, 15, 15, 15, 15, 15]) +shuffleI([6, 2, 0, 14, 6, 10, 11, 1]) + +shuffleU([7, 7, 7, 7, 7, 7, 7, 7]) +shuffleU([8, 15, 15, 15, 15, 15, 15, 15]) +shuffleU([6, 2, 0, 14, 6, 10, 11, 1]) + +// Test unary operators. +function unaryI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + `var fut = i16x8.${opname};` + + 'function f(v) { v = i16x8chk(v); return fut(v); } return f'), this); + let a = [65000-1,2,65000-3,4,65000-5,6,65000-7,65000-9]; + let v = SIMD.Int16x8(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +function unaryU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8CHK + U16x8I16x8 + I16x8U16x8 + + `var fut = u16x8.${opname};` + + 'function f(v) { v = i16x8chk(v); return i16x8u16x8(fut(u16x8i16x8(v))); } return f'), this); + let a = [65000-1,2,65000-3,4,65000-5,6,65000-7,65000-9]; + let v = SIMD.Int16x8(...a); + assertEqVecArr(SIMD.Uint16x8.fromInt16x8Bits(simdfunc(v)), a.map(lanefunc)); +} + +function unaryB(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + B16x8 + B16x8CHK + + `var fut = b16x8.${opname};` + + 'function f(v) { v = b16x8chk(v); return fut(v); } return f'), this); + let a = [1,1,0,1,1,0,0,0]; + let v = SIMD.Bool16x8(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +unaryI('not', x => ~x << 16 >> 16); +unaryU('not', x => ~x << 16 >>> 16); +unaryB('not', x => !x); +unaryI('neg', x => -x << 16 >> 16); +unaryU('neg', x => -x << 16 >>> 16); + + +// Test binary operators. +function zipmap(a1, a2, f) { + assertEq(a1.length, a2.length); + let r = []; + for (var i = 0; i < a1.length; i++) + r.push(f(a1[i], a2[i])); + return r +} + +function binaryI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + `var fut = i16x8.${opname};` + + 'function f(v1, v2) { v1 = i16x8chk(v1); v2 = i16x8chk(v2); return fut(v1, v2); } return f'), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +function binaryU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8CHK + U16x8I16x8 + I16x8U16x8 + + `var fut = u16x8.${opname};` + + 'function f(v1, v2) { v1 = i16x8chk(v1); v2 = i16x8chk(v2); return i16x8u16x8(fut(u16x8i16x8(v1), u16x8i16x8(v2))); } return f'), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >>> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >>> 16); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + let res = SIMD.Uint16x8.fromInt16x8Bits(simdfunc(v1, v2)); + assertEqVecArr(res, ref); +} + +function binaryB(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + B16x8 + B16x8CHK + + `var fut = b16x8.${opname};` + + 'function f(v1, v2) { v1 = b16x8chk(v1); v2 = b16x8chk(v2); return fut(v1, v2); } return f'), this); + let a = [1,1,0,1,1,0,0,0]; + let v = SIMD.Bool16x8(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +binaryI('add', (x, y) => (x + y) << 16 >> 16); +binaryI('sub', (x, y) => (x - y) << 16 >> 16); +binaryI('mul', (x, y) => (x * y) << 16 >> 16); +binaryU('add', (x, y) => (x + y) << 16 >>> 16); +binaryU('sub', (x, y) => (x - y) << 16 >>> 16); +binaryU('mul', (x, y) => (x * y) << 16 >>> 16); + +binaryI('and', (x, y) => (x & y) << 16 >> 16); +binaryI('or', (x, y) => (x | y) << 16 >> 16); +binaryI('xor', (x, y) => (x ^ y) << 16 >> 16); +binaryU('and', (x, y) => (x & y) << 16 >>> 16); +binaryU('or', (x, y) => (x | y) << 16 >>> 16); +binaryU('xor', (x, y) => (x ^ y) << 16 >>> 16); + +function sat(x, lo, hi) { + if (x < lo) return lo; + if (x > hi) return hi; + return x +} +function isat(x) { return sat(x, -32768, 32767); } +function usat(x) { return sat(x, 0, 0xffff); } + +binaryI('addSaturate', (x, y) => isat(x + y)) +binaryI('subSaturate', (x, y) => isat(x - y)) +binaryU('addSaturate', (x, y) => usat(x + y)) +binaryU('subSaturate', (x, y) => usat(x - y)) + + +// Test shift operators. +function zip1map(a, s, f) { + return a.map(x => f(x, s)); +} + +function shiftI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + `var fut = i16x8.${opname};` + + 'function f(v, s) { v = i16x8chk(v); s = s|0; return fut(v, s); } return f'), this); + let a = [-1,2,-3,0x80,0x7f,6,0x8000,0x7fff]; + let v = SIMD.Int16x8(...a); + for (let s of [0, 1, 2, 6, 7, 8, 9, 10, 16, 255, -1, -8, -7, -1000]) { + let ref = zip1map(a, s, lanefunc); + // 1. Test dynamic shift amount. + assertEqVecArr(simdfunc(v, s), ref); + + // 2. Test constant shift amount. + let cstf = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + `var fut = i16x8.${opname};` + + `function f(v) { v = i16x8chk(v); return fut(v, ${s}); } return f`), this); + assertEqVecArr(cstf(v, s), ref); + } +} + +function shiftU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8CHK + U16x8I16x8 + I16x8U16x8 + + `var fut = u16x8.${opname};` + + 'function f(v, s) { v = i16x8chk(v); s = s|0; return i16x8u16x8(fut(u16x8i16x8(v), s)); } return f'), this); + let a = [-1,2,-3,0x80,0x7f,6,0x8000,0x7fff]; + let v = SIMD.Int16x8(...a); + for (let s of [0, 1, 2, 6, 7, 8, 9, 10, 16, 255, -1, -8, -7, -1000]) { + let ref = zip1map(a, s, lanefunc); + // 1. Test dynamic shift amount. + assertEqVecArr(SIMD.Uint16x8.fromInt16x8Bits(simdfunc(v, s)), ref); + + // 2. Test constant shift amount. + let cstf = asmLink(asmCompile('glob', USE_ASM + U16x8 + I16x8 + I16x8CHK + U16x8I16x8 + I16x8U16x8 + + `var fut = u16x8.${opname};` + + `function f(v) { v = i16x8chk(v); return i16x8u16x8(fut(u16x8i16x8(v), ${s})); } return f`), this); + assertEqVecArr(SIMD.Uint16x8.fromInt16x8Bits(cstf(v, s)), ref); + } +} + +shiftI('shiftLeftByScalar', (x,s) => (x << (s & 15)) << 16 >> 16); +shiftU('shiftLeftByScalar', (x,s) => (x << (s & 15)) << 16 >>> 16); +shiftI('shiftRightByScalar', (x,s) => ((x << 16 >> 16) >> (s & 15)) << 16 >> 16); +shiftU('shiftRightByScalar', (x,s) => ((x << 16 >>> 16) >>> (s & 15)) << 16 >>> 16); + + +// Comparisons. +function compareI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + + `var fut = i16x8.${opname};` + + 'function f(v1, v2) { v1 = i16x8chk(v1); v2 = i16x8chk(v2); return fut(v1, v2); } return f'), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >> 16); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +function compareU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I16x8 + I16x8CHK + U16x8 + U16x8I16x8 + + `var fut = u16x8.${opname};` + + 'function f(v1, v2) { v1 = i16x8chk(v1); v2 = i16x8chk(v2); return fut(u16x8i16x8(v1), u16x8i16x8(v2)); } return f'), this); + let a1 = [ -1,2, -3,0x8000,0x7f,6,-7, 8].map(x => x << 16 >>> 16); + let a2 = [0x8000,2,0x8000,0x7fff, 0,0, 8,-9].map(x => x << 16 >>> 16); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int16x8(...a1); + let v2 = SIMD.Int16x8(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +compareI("equal", (x,y) => x == y); +compareU("equal", (x,y) => x == y); +compareI("notEqual", (x,y) => x != y); +compareU("notEqual", (x,y) => x != y); +compareI("lessThan", (x,y) => x < y); +compareU("lessThan", (x,y) => x < y); +compareI("lessThanOrEqual", (x,y) => x <= y); +compareU("lessThanOrEqual", (x,y) => x <= y); +compareI("greaterThan", (x,y) => x > y); +compareU("greaterThan", (x,y) => x > y); +compareI("greaterThanOrEqual", (x,y) => x >= y); +compareU("greaterThanOrEqual", (x,y) => x >= y); diff --git a/js/src/jit-test/tests/asm.js/testSIMD-8x16.js b/js/src/jit-test/tests/asm.js/testSIMD-8x16.js new file mode 100644 index 000000000000..c54645b074db --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD-8x16.js @@ -0,0 +1,539 @@ +load(libdir + "asm.js"); +load(libdir + "simd.js"); +load(libdir + "asserts.js"); + +// Set to true to see more JS debugging spew. +const DEBUG = false; + +if (!isSimdAvailable()) { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +// Tests for 8x16 SIMD types: Int8x16, Uint8x16, Bool8x16. + +const I8x16 = 'var i8x16 = glob.SIMD.Int8x16;' +const I8x16CHK = 'var i8x16chk = i8x16.check;' +const I8x16EXT = 'var i8x16ext = i8x16.extractLane;' +const I8x16REP = 'var i8x16rep = i8x16.replaceLane;' +const I8x16U8x16 = 'var i8x16u8x16 = i8x16.fromUint8x16Bits;' + +const U8x16 = 'var u8x16 = glob.SIMD.Uint8x16;' +const U8x16CHK = 'var u8x16chk = u8x16.check;' +const U8x16EXT = 'var u8x16ext = u8x16.extractLane;' +const U8x16REP = 'var u8x16rep = u8x16.replaceLane;' +const U8x16I8x16 = 'var u8x16i8x16 = u8x16.fromInt8x16Bits;' + +const B8x16 = 'var b8x16 = glob.SIMD.Bool8x16;' +const B8x16CHK = 'var b8x16chk = b8x16.check;' +const B8x16EXT = 'var b8x16ext = b8x16.extractLane;' +const B8x16REP = 'var b8x16rep = b8x16.replaceLane;' + +const INT8_MAX = 127 +const INT8_MIN = -128 +const UINT8_MAX = 255 + +// Linking +assertEq(asmLink(asmCompile('glob', USE_ASM + I8x16 + "function f() {} return f"), {SIMD:{Int8x16: SIMD.Int8x16}})(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + U8x16 + "function f() {} return f"), {SIMD:{Uint8x16: SIMD.Uint8x16}})(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + B8x16 + "function f() {} return f"), {SIMD:{Bool8x16: SIMD.Bool8x16}})(), undefined); + +// Local variable of Int8x16 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Int8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16;} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16();} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);} return f"); +assertAsmTypeFail('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16|0);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + I8x16 + "function f() {var x=i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15," + (INT8_MAX + 1) + ");} return f"), this)(), undefined); + +// Local variable of Uint8x16 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Uint8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16;} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16();} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16|0);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + U8x16 + "function f() {var x=u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15," + (UINT8_MAX + 1) + ");} return f"), this)(), undefined); + +// Local variable of Bool8x16 type. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Bool8x16(1,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1);} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16;} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16();} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1,0,0,0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1|0);} return f"); +assertAsmTypeFail('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + B8x16 + "function f() {var x=b8x16(1,0,0,0,0,0,0,0,0,1,-1,2,-2,1,1,1);} return f"), this)(), undefined); + +// Global variable of Int8x16 type. +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + I8x16 + I8x16CHK + "var g=i8x16chk(ffi.g); function f() { return i8x16chk(g); } return f"), this, + {g: SIMD.Int8x16(1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17)})(), [1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17]); +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + I8x16 + I8x16CHK + "var g=i8x16chk(ffi.g); function f() { g=i8x16(5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8); return i8x16chk(g); } return f"), this, + {g: SIMD.Int8x16(1,2,3,4,5,6,7,8,10,11,12,13,14,15,16,17)})(), [5,6,7,8,9,10,11,12,1,2,3,4,5,6,7,8]); + +// Global variable of Bool8x16 type. +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + B8x16 + B8x16CHK + "var g=b8x16chk(ffi.g); function f() { return b8x16chk(g); } return f"), this, + {g: SIMD.Bool8x16(1,1,0,1,0,0,1,0,0,1,0,1,0,0,1,0)})(), [true,true,false,true,false,false,true,false,false,true,false,true,false,false,true,false]); +assertEqVecArr(asmLink(asmCompile('glob', 'ffi', USE_ASM + B8x16 + B8x16CHK + "var g=b8x16chk(ffi.g); function f() { g=b8x16(1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,0); return b8x16chk(g); } return f"), this, + {g: SIMD.Bool8x16(1,1,0,1,0,0,1,0)})(), [true,true,false,true,false,true,true,true,false,true,false,true,true,true,false,false]); + +// Unsigned SIMD globals are not allowed. +assertAsmTypeFail('glob', 'ffi', USE_ASM + U8x16 + U8x16CHK + "var g=u8x16chk(ffi.g); function f() { } return f"); + +// Only signed Int8x16 allowed as return value. +assertEqVecArr(asmLink(asmCompile('glob', USE_ASM + I8x16 + "function f() {return i8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"), this)(), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); +assertEqVecArr(asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + "function f() {return i8x16chk(i8x16(1,2,3,132,5,6,7,8,9,10,11,12,13,14,15,16));} return f"), this)(), + [1, 2, 3, -124, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); +assertAsmTypeFail('glob', USE_ASM + U8x16 + "function f() {return u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);} return f"); +assertAsmTypeFail('glob', USE_ASM + U8x16 + U8x16CHK + "function f() {return u8x16chk(u8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16));} return f"); + +// Test splat. +function splat(x) { + let r = [] + for (let i = 0; i < 16; i++) + r.push(x); + return r +} + +splatB = asmLink(asmCompile('glob', USE_ASM + B8x16 + + 'var splat = b8x16.splat;' + + 'function f(x) { x = x|0; return splat(x); } return f'), this); +assertEqVecArr(splatB(true), splat(true)); +assertEqVecArr(splatB(false), splat(false)); + + +splatB0 = asmLink(asmCompile('glob', USE_ASM + B8x16 + + 'var splat = b8x16.splat;' + + 'function f() { var x = 0; return splat(x); } return f'), this); +assertEqVecArr(splatB0(), splat(false)); +splatB1 = asmLink(asmCompile('glob', USE_ASM + B8x16 + + 'var splat = b8x16.splat;' + + 'function f() { var x = 1; return splat(x); } return f'), this); +assertEqVecArr(splatB1(), splat(true)); + +splatI = asmLink(asmCompile('glob', USE_ASM + I8x16 + + 'var splat = i8x16.splat;' + + 'function f(x) { x = x|0; return splat(x); } return f'), this); +for (let x of [0, 1, -1, 0x1234, 0x12, 1000, -1000000]) { + assertEqVecArr(splatI(x), splat(x << 24 >> 24)); +} + +splatIc = asmLink(asmCompile('glob', USE_ASM + I8x16 + + 'var splat = i8x16.splat;' + + 'function f() { var x = 100; return splat(x); } return f'), this); +assertEqVecArr(splatIc(), splat(100)) + +splatU = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16U8x16 + + 'var splat = u8x16.splat;' + + 'function f(x) { x = x|0; return i8x16u8x16(splat(x)); } return f'), this); +for (let x of [0, 1, -1, 0x1234, 0x12, 1000, -1000000]) { + assertEqVecArr(SIMD.Uint8x16.fromInt8x16Bits(splatI(x)), splat(x << 24 >>> 24)); +} + +splatUc = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16U8x16 + + 'var splat = u8x16.splat;' + + 'function f() { var x = 200; return i8x16u8x16(splat(x)); } return f'), this); +assertEqVecArr(SIMD.Uint8x16.fromInt8x16Bits(splatUc()), splat(200)) + + +// Test extractLane. +// +// The lane index must be a literal int, and we generate different code for +// different lanes. +function extractI(a, i) { + return asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16EXT + + `function f() {var x=i8x16(${a.join(',')}); return i8x16ext(x, ${i})|0; } return f`), this)(); +} +a = [-1,2,-3,4,-5,6,-7,8,-9,10,-11,12,-13,-14,-15,-16]; +for (var i = 0; i < 16; i++) + assertEq(extractI(a, i), a[i]); +a = a.map(x => -x); +for (var i = 0; i < 16; i++) + assertEq(extractI(a, i), a[i]); + +function extractU(a, i) { + return asmLink(asmCompile('glob', USE_ASM + U8x16 + U8x16EXT + + `function f() {var x=u8x16(${a.join(',')}); return u8x16ext(x, ${i})|0; } return f`), this)(); +} +a = [1,255,12,13,14,150,200,3,4,5,6,7,8,9,10,16]; +for (var i = 0; i < 16; i++) + assertEq(extractU(a, i), a[i]); +a = a.map(x => 255-x); +for (var i = 0; i < 16; i++) + assertEq(extractU(a, i), a[i]); + +function extractB(a, i) { + return asmLink(asmCompile('glob', USE_ASM + B8x16 + B8x16EXT + + `function f() {var x=b8x16(${a.join(',')}); return b8x16ext(x, ${i})|0; } return f`), this)(); +} +a = [1,1,0,1,1,0,0,0,1,1,1,1,0,0,0,1]; +for (var i = 0; i < 16; i++) + assertEq(extractB(a, i), a[i]); +a = a.map(x => 1-x); +for (var i = 0; i < 16; i++) + assertEq(extractB(a, i), a[i]); + +// Test replaceLane. +function replaceI(a, i) { + return asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16REP + + `function f(v) {v=v|0; var x=i8x16(${a.join(',')}); return i8x16rep(x,${i},v); } return f`), this); +} +a = [-1,2,-3,4,-5,6,-7,8,-9,10,-11,12,-13,-14,-15,-16]; +for (var i = 0; i < 16; i++) { + var f = replaceI(a, i); + var b = a.slice(0); + b[i] = -20; + assertEqVecArr(f(-20), b); +} + +function replaceU(a, i) { + return asmLink(asmCompile('glob', USE_ASM + U8x16 + U8x16REP + I8x16 + I8x16U8x16 + + `function f(v) {v=v|0; var x=u8x16(${a.join(',')}); x=u8x16rep(x,${i},v); return i8x16u8x16(x); } return f`), this); +} +a = [256-1,2,256-3,4,256-5,6,256-7,8,256-9,10,256-11,12,256-13,256-14,256-15,256-16]; +for (var i = 0; i < 16; i++) { + // Result returned as Int8x16, convert back. + var rawf = replaceU(a, i); + var f = x => SIMD.Uint8x16.fromInt8x16Bits(rawf(x)); + var b = a.slice(0); + b[i] = 100; + assertEqVecArr(f(100), b); +} + +function replaceB(a, i) { + return asmLink(asmCompile('glob', USE_ASM + B8x16 + B8x16REP + + `function f(v) {v=v|0; var x=b8x16(${a.join(',')}); return b8x16rep(x,${i},v); } return f`), this); +} +a = [1,1,0,1,1,0,0,0,1,1,1,1,0,0,0,1]; +for (var i = 0; i < 16; i++) { + var f = replaceB(a, i); + var b = a.slice(0); + v = 1 - a[i]; + b[i] = v; + assertEqVecArr(f(v), b.map(x => !!x)); +} + + +// Test select. +selectI = asmLink(asmCompile('glob', USE_ASM + I8x16 + B8x16 + B8x16CHK + + 'var select = i8x16.select;' + + 'var a = i8x16(-1,2,-3,4,-5, 6,-7, 8,-9,10,-11,12,-13,-14,-15,-16);' + + 'var b = i8x16( 5,6, 7,8, 9,10,11,12,13,14, 15,16,-77, 45, 32, 0);' + + 'function f(x) { x = b8x16chk(x); return select(x, a, b); } return f'), this); +assertEqVecArr(selectI(SIMD.Bool8x16( 0,0, 1,0, 1,1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1)), + [ 5,6,-3,8,-5,6,-7,12,-9,10,15,16,-13,-14,32,-16]); + +selectU = asmLink(asmCompile('glob', USE_ASM + I8x16 + B8x16 + B8x16CHK + U8x16 + I8x16U8x16 + U8x16I8x16 + + 'var select = u8x16.select;' + + 'var a = i8x16(-1,2,-3,4,-5, 6,-7, 8,-9,10,-11,12,-13,-14,-15,-16);' + + 'var b = i8x16( 5,6, 7,8, 9,10,11,12,13,14, 15,16,-77, 45, 32, 0);' + + 'function f(x) { x = b8x16chk(x); return i8x16u8x16(select(x, u8x16i8x16(a), u8x16i8x16(b))); } return f'), this); +assertEqVecArr(selectU(SIMD.Bool8x16( 0,0, 1,0, 1,1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1)), + [ 5,6,-3,8,-5,6,-7,12,-9,10,15,16,-13,-14,32,-16]); + + +// Test swizzle. +function swizzle(vec, lanes) { + let r = []; + for (let i = 0; i < 16; i++) + r.push(vec[lanes[i]]); + return r; +} + +function swizzleI(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + 'var swz = i8x16.swizzle;' + + `function f(a) { a = i8x16chk(a); return swz(a, ${lanes.join()}); } return f`), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(asm(v1), swizzle(a1, lanes)); + assertEqVecArr(asm(v2), swizzle(a2, lanes)); +} + +swizzleI([10, 1, 7, 5, 1, 2, 6, 8, 5, 13, 0, 6, 2, 8, 0, 9]); +swizzleI([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +swizzleI([15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15]); + +function swizzleU(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + U8x16 + U8x16I8x16 + I8x16U8x16 + + 'var swz = u8x16.swizzle;' + + `function f(a) { a = i8x16chk(a); return i8x16u8x16(swz(u8x16i8x16(a), ${lanes.join()})); } return f`), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(asm(v1), swizzle(a1, lanes)); + assertEqVecArr(asm(v2), swizzle(a2, lanes)); +} + +swizzleU([10, 1, 7, 5, 1, 2, 6, 8, 5, 13, 0, 6, 2, 8, 0, 9]); +swizzleU([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +swizzleU([15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15]); + +// Out-of-range lane indexes. +assertAsmTypeFail('glob', USE_ASM + I8x16 + 'var swz = i8x16.swizzle; ' + + 'function f() { var x=i8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); } return f'); +assertAsmTypeFail('glob', USE_ASM + U8x16 + 'var swz = u8x16.swizzle; ' + + 'function f() { var x=u8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); } return f'); +// Missing lane indexes. +assertAsmTypeFail('glob', USE_ASM + I8x16 + 'var swz = i8x16.swizzle; ' + + 'function f() { var x=i8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); } return f'); +assertAsmTypeFail('glob', USE_ASM + U8x16 + 'var swz = u8x16.swizzle; ' + + 'function f() { var x=u8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); swz(x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); } return f'); + + +// Test shuffle. +function shuffle(vec1, vec2, lanes) { + let r = []; + let vec = vec1.concat(vec2); + for (let i = 0; i < 16; i++) + r.push(vec[lanes[i]]); + return r; +} + +function shuffleI(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + 'var shuf = i8x16.shuffle;' + + `function f(a1, a2) { a1 = i8x16chk(a1); a2 = i8x16chk(a2); return shuf(a1, a2, ${lanes.join()}); } return f`), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(asm(v1, v2), shuffle(a1, a2, lanes)); +} + +shuffleI([31, 9, 5, 4, 29, 12, 19, 10, 16, 22, 10, 9, 6, 18, 9, 8]); +shuffleI([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +shuffleI([31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31]); + +function shuffleU(lanes) { + let asm = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + U8x16 + U8x16I8x16 + I8x16U8x16 + + 'var shuf = u8x16.shuffle;' + + 'function f(a1, a2) { a1 = i8x16chk(a1); a2 = i8x16chk(a2); ' + + `return i8x16u8x16(shuf(u8x16i8x16(a1), u8x16i8x16(a2), ${lanes.join()})); } return f`), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(asm(v1, v2), shuffle(a1, a2, lanes)); +} + +shuffleU([31, 9, 5, 4, 29, 12, 19, 10, 16, 22, 10, 9, 6, 18, 9, 8]); +shuffleU([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); +shuffleU([31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31]); + + +// Out-of-range lane indexes. +assertAsmTypeFail('glob', USE_ASM + I8x16 + 'var shuf = i8x16.shuffle; ' + + 'function f() { var x=i8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); shuf(x,x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,32); } return f'); +assertAsmTypeFail('glob', USE_ASM + U8x16 + 'var shuf = u8x16.shuffle; ' + + 'function f() { var x=u8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); shuf(x,x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,32); } return f'); +// Missing lane indexes. +assertAsmTypeFail('glob', USE_ASM + I8x16 + 'var shuf = i8x16.shuffle; ' + + 'function f() { var x=i8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); shuf(x,x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); } return f'); +assertAsmTypeFail('glob', USE_ASM + U8x16 + 'var shuf = u8x16.shuffle; ' + + 'function f() { var x=u8x16(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); shuf(x,x,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); } return f'); + + +// Test unary operators. +function unaryI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + `var fut = i8x16.${opname};` + + 'function f(v) { v = i8x16chk(v); return fut(v); } return f'), this); + let a = [-1,2,-3,4,-5,6,-7,8,-9,10,-11,12,-13,-14,-15,-16]; + let v = SIMD.Int8x16(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +function unaryU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16CHK + U8x16I8x16 + I8x16U8x16 + + `var fut = u8x16.${opname};` + + 'function f(v) { v = i8x16chk(v); return i8x16u8x16(fut(u8x16i8x16(v))); } return f'), this); + let a = [256-1,2,256-3,4,256-5,6,256-7,8,256-9,10,256-11,12,256-13,256-14,256-15,256-16]; + let v = SIMD.Int8x16(...a); + assertEqVecArr(SIMD.Uint8x16.fromInt8x16Bits(simdfunc(v)), a.map(lanefunc)); +} + +function unaryB(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + B8x16 + B8x16CHK + + `var fut = b8x16.${opname};` + + 'function f(v) { v = b8x16chk(v); return fut(v); } return f'), this); + let a = [1,1,0,1,1,0,0,0,1,1,1,1,0,0,0,1]; + let v = SIMD.Bool8x16(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +unaryI('not', x => ~x << 24 >> 24); +unaryU('not', x => ~x << 24 >>> 24); +unaryB('not', x => !x); +unaryI('neg', x => -x << 24 >> 24); +unaryU('neg', x => -x << 24 >>> 24); + + +// Test binary operators. +function zipmap(a1, a2, f) { + assertEq(a1.length, a2.length); + let r = []; + for (var i = 0; i < a1.length; i++) + r.push(f(a1[i], a2[i])); + return r +} + +function binaryI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + `var fut = i8x16.${opname};` + + 'function f(v1, v2) { v1 = i8x16chk(v1); v2 = i8x16chk(v2); return fut(v1, v2); } return f'), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +function binaryU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16CHK + U8x16I8x16 + I8x16U8x16 + + `var fut = u8x16.${opname};` + + 'function f(v1, v2) { v1 = i8x16chk(v1); v2 = i8x16chk(v2); return i8x16u8x16(fut(u8x16i8x16(v1), u8x16i8x16(v2))); } return f'), this); + let a1 = [ -1,2, -3,0x80,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16].map(x => x & 0xff); + let a2 = [0x80,2,0x80,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16,0xff].map(x => x & 0xff); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + let res = SIMD.Uint8x16.fromInt8x16Bits(simdfunc(v1, v2)); + assertEqVecArr(res, ref); +} + +function binaryB(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + B8x16 + B8x16CHK + + `var fut = b8x16.${opname};` + + 'function f(v1, v2) { v1 = b8x16chk(v1); v2 = b8x16chk(v2); return fut(v1, v2); } return f'), this); + let a = [1,1,0,1,1,0,0,0,1,1,1,1,0,0,0,1]; + let v = SIMD.Bool8x16(...a); + assertEqVecArr(simdfunc(v), a.map(lanefunc)); +} + +binaryI('add', (x, y) => (x + y) << 24 >> 24); +binaryI('sub', (x, y) => (x - y) << 24 >> 24); +binaryI('mul', (x, y) => (x * y) << 24 >> 24); +binaryU('add', (x, y) => (x + y) << 24 >>> 24); +binaryU('sub', (x, y) => (x - y) << 24 >>> 24); +binaryU('mul', (x, y) => (x * y) << 24 >>> 24); + +binaryI('and', (x, y) => (x & y) << 24 >> 24); +binaryI('or', (x, y) => (x | y) << 24 >> 24); +binaryI('xor', (x, y) => (x ^ y) << 24 >> 24); +binaryU('and', (x, y) => (x & y) << 24 >>> 24); +binaryU('or', (x, y) => (x | y) << 24 >>> 24); +binaryU('xor', (x, y) => (x ^ y) << 24 >>> 24); + +function sat(x, lo, hi) { + if (x < lo) return lo; + if (x > hi) return hi; + return x +} +function isat(x) { return sat(x, -128, 127); } +function usat(x) { return sat(x, 0, 255); } + +binaryI('addSaturate', (x, y) => isat(x + y)) +binaryI('subSaturate', (x, y) => isat(x - y)) +binaryU('addSaturate', (x, y) => usat(x + y)) +binaryU('subSaturate', (x, y) => usat(x - y)) + +// Test shift operators. +function zip1map(a, s, f) { + return a.map(x => f(x, s)); +} + +function shiftI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + `var fut = i8x16.${opname};` + + 'function f(v, s) { v = i8x16chk(v); s = s|0; return fut(v, s); } return f'), this); + let a = [0x80,2,0x80,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16,0xff]; + let v = SIMD.Int8x16(...a); + for (let s of [0, 1, 2, 6, 7, 8, 9, 10, 16, 255, -1, -8, -7, -1000]) { + let ref = zip1map(a, s, lanefunc); + // 1. Test dynamic shift amount. + assertEqVecArr(simdfunc(v, s), ref); + + // 2. Test constant shift amount. + let cstf = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + `var fut = i8x16.${opname};` + + `function f(v) { v = i8x16chk(v); return fut(v, ${s}); } return f`), this); + assertEqVecArr(cstf(v, s), ref); + } +} + +function shiftU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16CHK + U8x16I8x16 + I8x16U8x16 + + `var fut = u8x16.${opname};` + + 'function f(v, s) { v = i8x16chk(v); s = s|0; return i8x16u8x16(fut(u8x16i8x16(v), s)); } return f'), this); + let a = [0x80,2,0x80,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16,0xff]; + let v = SIMD.Int8x16(...a); + for (let s of [0, 1, 2, 6, 7, 8, 9, 10, 16, 255, -1, -8, -7, -1000]) { + let ref = zip1map(a, s, lanefunc); + // 1. Test dynamic shift amount. + assertEqVecArr(SIMD.Uint8x16.fromInt8x16Bits(simdfunc(v, s)), ref); + + // 2. Test constant shift amount. + let cstf = asmLink(asmCompile('glob', USE_ASM + U8x16 + I8x16 + I8x16CHK + U8x16I8x16 + I8x16U8x16 + + `var fut = u8x16.${opname};` + + `function f(v) { v = i8x16chk(v); return i8x16u8x16(fut(u8x16i8x16(v), ${s})); } return f`), this); + assertEqVecArr(SIMD.Uint8x16.fromInt8x16Bits(cstf(v, s)), ref); + } +} + +shiftI('shiftLeftByScalar', (x,s) => (x << (s & 7)) << 24 >> 24); +shiftU('shiftLeftByScalar', (x,s) => (x << (s & 7)) << 24 >>> 24); +shiftI('shiftRightByScalar', (x,s) => ((x << 24 >> 24) >> (s & 7)) << 24 >> 24); +shiftU('shiftRightByScalar', (x,s) => ((x << 24 >>> 24) >>> (s & 7)) << 24 >>> 24); + + +// Comparisons. +function compareI(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + + `var fut = i8x16.${opname};` + + 'function f(v1, v2) { v1 = i8x16chk(v1); v2 = i8x16chk(v2); return fut(v1, v2); } return f'), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16]; + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1]; + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +function compareU(opname, lanefunc) { + let simdfunc = asmLink(asmCompile('glob', USE_ASM + I8x16 + I8x16CHK + U8x16 + U8x16I8x16 + + `var fut = u8x16.${opname};` + + 'function f(v1, v2) { v1 = i8x16chk(v1); v2 = i8x16chk(v2); return fut(u8x16i8x16(v1), u8x16i8x16(v2)); } return f'), this); + let a1 = [ -1,2, -3,-128,0x7f,6,-7, 8,-9, 10,-11, 12,-13,-14,-15, -16].map(x => x << 24 >>> 24); + let a2 = [-128,2,-128,0x7f, 0,0, 8,-9,10,-11, 12,-13,-14,-15,-16, -1].map(x => x << 24 >>> 24); + let ref = zipmap(a1, a2, lanefunc); + let v1 = SIMD.Int8x16(...a1); + let v2 = SIMD.Int8x16(...a2); + assertEqVecArr(simdfunc(v1, v2), ref); +} + +compareI("equal", (x,y) => x == y); +compareU("equal", (x,y) => x == y); +compareI("notEqual", (x,y) => x != y); +compareU("notEqual", (x,y) => x != y); +compareI("lessThan", (x,y) => x < y); +compareU("lessThan", (x,y) => x < y); +compareI("lessThanOrEqual", (x,y) => x <= y); +compareU("lessThanOrEqual", (x,y) => x <= y); +compareI("greaterThan", (x,y) => x > y); +compareU("greaterThan", (x,y) => x > y); +compareI("greaterThanOrEqual", (x,y) => x >= y); +compareU("greaterThanOrEqual", (x,y) => x >= y); diff --git a/js/src/jit-test/tests/asm.js/testSIMD-bitcasts.js b/js/src/jit-test/tests/asm.js/testSIMD-bitcasts.js new file mode 100644 index 000000000000..c5a5fb2bcada --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD-bitcasts.js @@ -0,0 +1,84 @@ +load(libdir + "asm.js"); +load(libdir + "simd.js"); +load(libdir + "asserts.js"); + +// Set to true to see more JS debugging spew. +const DEBUG = false; + +if (!isSimdAvailable()) { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +// Test all bit-casts and normal loads and stores. +var heap = new ArrayBuffer(BUF_MIN); +var asU8 = new Uint8Array(heap); +var allTypes = [ + "Int8x16", + "Int16x8", + "Int32x4", + "Uint8x16", + "Uint16x8", + "Uint32x4", + "Float32x4" +]; + +// Generate a load bit-cast store test function that performs: +// +// function f(a, b) { +// vec = src.load(H, a); +// cast = dst.from«src»Bits(vec); +// store(H, b, cast); +// } +// +// Here, `H` is the heap provided by `heap`. +function test_func(src, dst) { + text = ` + "use asm"; + var src = glob.SIMD.${src}; + var dst = glob.SIMD.${dst}; + var ld = src.load; + var st = dst.store; + var bc = dst.from${src}Bits; + + var H = new glob.Uint8Array(heap); + + function f(a, b) { + a = a|0; + b = b|0; + + st(H, b, bc(ld(H, a))); + } + + return f; + `; + return asmLink(asmCompile('glob', 'ffi', 'heap', text), this, null, heap); +} + +function assertBuf16(a, b) { + for (let i=0; i < 16; i++) { + assertEq(asU8[a+i], asU8[b+i]); + } +} + +for (let src of allTypes) { + for (let dst of allTypes) { + // Skip identity conversions. + if (src == dst) continue; + + print(src, dst); + let f = test_func(src, dst); + // Initialize with pseudo-random data. + for (let i = 0; i < 64; i++) { + asU8[i] = (i + 17) * 97; + } + + // Aligned load/store. + f(0, 16); + assertBuf16(0, 16); + + // Unaligned access. + f(1, 27); + assertBuf16(1, 27); + } +} diff --git a/js/src/jit-test/tests/asm.js/testSIMD-load-store.js b/js/src/jit-test/tests/asm.js/testSIMD-load-store.js new file mode 100644 index 000000000000..d826c106baec --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD-load-store.js @@ -0,0 +1,457 @@ +// |jit-test| +load(libdir + "asm.js"); +load(libdir + "simd.js"); +load(libdir + "asserts.js"); + +// Avoid pathological --ion-eager compile times due to bails in loops +setJitCompilerOption('ion.warmup.trigger', 1000000); + +// Set to true to see more JS debugging spew +const DEBUG = false; + +if (!isSimdAvailable() || typeof SIMD === 'undefined' || !isAsmJSCompilationAvailable()) { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +const RuntimeError = WebAssembly.RuntimeError; + +const INT32_MAX = Math.pow(2, 31) - 1; +const INT32_MIN = INT32_MAX + 1 | 0; + +try { + +// Load / Store +var IMPORTS = USE_ASM + 'var H=new glob.Uint8Array(heap); var i4=glob.SIMD.Int32x4; var ci4=i4.check; var load=i4.load; var store=i4.store;'; + +// Bad number of args +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load();} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3);} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3, 4, 5);} return f"); + +// Bad type of args +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(3, 5);} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, 5.0);} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0.;load(H, i);} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var H2=new glob.Int32Array(heap); function f(){var i=0;load(H2, i)} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var H2=42; function f(){var i=0;load(H2, i)} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;load(H2, i)} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "var f4=glob.SIMD.Float32x4; function f(){var i=0;var vec=f4(1,2,3,4); store(H, i, vec)} return f"); + +// Bad coercions of returned values +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;return load(H, i)|0;} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){var i=0;return +load(H, i);} return f"); + +// Literal index constants +var buf = new ArrayBuffer(BUF_MIN); +var SIZE_TA = BUF_MIN >> 2 +var asI32 = new Int32Array(buf); +asI32[SIZE_TA - 4] = 4; +asI32[SIZE_TA - 3] = 3; +asI32[SIZE_TA - 2] = 2; +asI32[SIZE_TA - 1] = 1; + +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, -1);} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1) + ");} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1 - 15) + ");} return f"); +asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f(){load(H, " + (INT32_MAX + 1 - 16) + ");} return f"); + +assertAsmLinkFail(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return ci4(load(H, " + (BUF_MIN - 15) + "));} return f"), this, {}, buf); +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return ci4(load(H, " + (BUF_MIN - 16) + "));} return f"), this, {}, buf)(), [4, 3, 2, 1]); +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', IMPORTS + "function f() {return ci4(load(H, " + BUF_MIN + " - 16 | 0));} return f"), this, {}, buf)(), [4, 3, 2, 1]); + +var CONSTANT_INDEX = 42; +var CONSTANT_BYTE_INDEX = CONSTANT_INDEX << 2; + +var loadStoreCode = ` + "use asm"; + + var H = new glob.Uint8Array(heap); + + var i4 = glob.SIMD.Int32x4; + var i4load = i4.load; + var i4store = i4.store; + var ci4 = i4.check; + + var f4 = glob.SIMD.Float32x4; + var f4load = f4.load; + var f4store = f4.store; + var cf4 = f4.check; + + function f32l(i) { i=i|0; return cf4(f4load(H, i|0)); } + function f32lcst() { return cf4(f4load(H, ${CONSTANT_BYTE_INDEX})); } + function f32s(i, vec) { i=i|0; vec=cf4(vec); f4store(H, i|0, vec); } + function f32scst(vec) { vec=cf4(vec); f4store(H, ${CONSTANT_BYTE_INDEX}, vec); } + + function i32l(i) { i=i|0; return ci4(i4load(H, i|0)); } + function i32lcst() { return ci4(i4load(H, ${CONSTANT_BYTE_INDEX})); } + function i32s(i, vec) { i=i|0; vec=ci4(vec); i4store(H, i|0, vec); } + function i32scst(vec) { vec=ci4(vec); i4store(H, ${CONSTANT_BYTE_INDEX}, vec); } + + function f32lbndcheck(i) { + i=i|0; + if ((i|0) > ${CONSTANT_BYTE_INDEX}) i=${CONSTANT_BYTE_INDEX}; + if ((i|0) < 0) i = 0; + return cf4(f4load(H, i|0)); + } + function f32sbndcheck(i, vec) { + i=i|0; + vec=cf4(vec); + if ((i|0) > ${CONSTANT_BYTE_INDEX}) i=${CONSTANT_BYTE_INDEX}; + if ((i|0) < 0) i = 0; + return cf4(f4store(H, i|0, vec)); + } + + return { + f32l: f32l, + f32lcst: f32lcst, + f32s: f32s, + f32scst: f32scst, + f32lbndcheck: f32lbndcheck, + f32sbndcheck: f32sbndcheck, + i32l: i32l, + i32lcst: i32lcst, + i32s: i32s, + i32scst: i32scst + } +`; + +const SIZE = 0x8000; + +var F32 = new Float32Array(SIZE); +var reset = function() { + for (var i = 0; i < SIZE; i++) + F32[i] = i + 1; +}; +reset(); + +var buf = F32.buffer; +var m = asmLink(asmCompile('glob', 'ffi', 'heap', loadStoreCode), this, null, buf); + +function slice(TA, i, n) { return Array.prototype.slice.call(TA, i, i + n); } + +// Float32x4.load +function f32l(n) { return m.f32l((n|0) << 2 | 0); }; + +// Correct accesses +assertEqX4(f32l(0), slice(F32, 0, 4)); +assertEqX4(f32l(1), slice(F32, 1, 4)); +assertEqX4(f32l(SIZE - 4), slice(F32, SIZE - 4, 4)); + +assertEqX4(m.f32lcst(), slice(F32, CONSTANT_INDEX, 4)); +assertEqX4(m.f32lbndcheck(CONSTANT_BYTE_INDEX), slice(F32, CONSTANT_INDEX, 4)); + +// OOB +assertThrowsInstanceOf(() => f32l(-1), RuntimeError); +assertThrowsInstanceOf(() => f32l(SIZE), RuntimeError); +assertThrowsInstanceOf(() => f32l(SIZE - 1), RuntimeError); +assertThrowsInstanceOf(() => f32l(SIZE - 2), RuntimeError); +assertThrowsInstanceOf(() => f32l(SIZE - 3), RuntimeError); + +var code = ` + "use asm"; + var f4 = glob.SIMD.Float32x4; + var f4l = f4.load; + var u8 = new glob.Uint8Array(heap); + + function g(x) { + x = x|0; + // set a constraint on the size of the heap + var ptr = 0; + ptr = u8[0xFFFF] | 0; + // give a precise range to x + x = (x>>0) > 5 ? 5 : x; + x = (x>>0) < 0 ? 0 : x; + // ptr value gets a precise range but the bounds check shouldn't get + // eliminated. + return f4l(u8, 0xFFFA + x | 0); + } + + return g; +`; +assertThrowsInstanceOf(() => asmLink(asmCompile('glob', 'ffi', 'heap', code), this, {}, new ArrayBuffer(0x10000))(0), RuntimeError); + +// Float32x4.store +function f32s(n, v) { return m.f32s((n|0) << 2 | 0, v); }; + +var vec = SIMD.Float32x4(5,6,7,8); +var vec2 = SIMD.Float32x4(0,1,2,3); +var vecWithNaN = SIMD.Float32x4(NaN, 2, NaN, 4); + +reset(); +f32s(0, vec); +assertEqX4(vec, slice(F32, 0, 4)); + +reset(); +f32s(0, vec2); +assertEqX4(vec2, slice(F32, 0, 4)); + +reset(); +f32s(4, vec); +assertEqX4(vec, slice(F32, 4, 4)); + +reset(); +f32s(4, vecWithNaN); +assertEqX4(vecWithNaN, slice(F32, 4, 4)); + +reset(); +m.f32scst(vec2); +assertEqX4(vec2, slice(F32, CONSTANT_INDEX, 4)); + +reset(); +m.f32sbndcheck(CONSTANT_BYTE_INDEX, vec); +assertEqX4(vec, slice(F32, CONSTANT_INDEX, 4)); + +// OOB +reset(); +assertThrowsInstanceOf(() => f32s(SIZE - 3, vec), RuntimeError); +assertThrowsInstanceOf(() => f32s(SIZE - 2, vec), RuntimeError); +assertThrowsInstanceOf(() => f32s(SIZE - 1, vec), RuntimeError); +assertThrowsInstanceOf(() => f32s(SIZE, vec), RuntimeError); +for (var i = 0; i < SIZE; i++) + assertEq(F32[i], i + 1); + +// Int32x4.load +var I32 = new Int32Array(buf); +reset = function () { + for (var i = 0; i < SIZE; i++) + I32[i] = i + 1; +}; +reset(); + +function i32(n) { return m.i32l((n|0) << 2 | 0); }; + +// Correct accesses +assertEqX4(i32(0), slice(I32, 0, 4)); +assertEqX4(i32(1), slice(I32, 1, 4)); +assertEqX4(i32(SIZE - 4), slice(I32, SIZE - 4, 4)); + +assertEqX4(m.i32lcst(), slice(I32, CONSTANT_INDEX, 4)); + +// OOB +assertThrowsInstanceOf(() => i32(-1), RuntimeError); +assertThrowsInstanceOf(() => i32(SIZE), RuntimeError); +assertThrowsInstanceOf(() => i32(SIZE - 1), RuntimeError); +assertThrowsInstanceOf(() => i32(SIZE - 2), RuntimeError); +assertThrowsInstanceOf(() => i32(SIZE - 3), RuntimeError); + +// Int32x4.store +function i32s(n, v) { return m.i32s((n|0) << 2 | 0, v); }; + +var vec = SIMD.Int32x4(5,6,7,8); +var vec2 = SIMD.Int32x4(0,1,2,3); + +reset(); +i32s(0, vec); +assertEqX4(vec, slice(I32, 0, 4)); + +reset(); +i32s(0, vec2); +assertEqX4(vec2, slice(I32, 0, 4)); + +reset(); +i32s(4, vec); +assertEqX4(vec, slice(I32, 4, 4)); + +reset(); +m.i32scst(vec2); +assertEqX4(vec2, slice(I32, CONSTANT_INDEX, 4)); + +// OOB +reset(); +assertThrowsInstanceOf(() => i32s(SIZE - 3, vec), RuntimeError); +assertThrowsInstanceOf(() => i32s(SIZE - 2, vec), RuntimeError); +assertThrowsInstanceOf(() => i32s(SIZE - 1, vec), RuntimeError); +assertThrowsInstanceOf(() => i32s(SIZE - 0, vec), RuntimeError); +for (var i = 0; i < SIZE; i++) + assertEq(I32[i], i + 1); + +// Partial loads and stores +(function() { + +// Variable indexes +function MakeCodeFor(typeName) { + return ` + "use asm"; + var type = glob.SIMD.${typeName}; + var c = type.check; + + var l1 = type.load1; + var l2 = type.load2; + + var s1 = type.store1; + var s2 = type.store2; + + var u8 = new glob.Uint8Array(heap); + + function load1(i) { i=i|0; return l1(u8, i); } + function load2(i) { i=i|0; return l2(u8, i); } + + function loadCst1() { return l1(u8, 41 << 2); } + function loadCst2() { return l2(u8, 41 << 2); } + + function store1(i, x) { i=i|0; x=c(x); return s1(u8, i, x); } + function store2(i, x) { i=i|0; x=c(x); return s2(u8, i, x); } + + function storeCst1(x) { x=c(x); return s1(u8, 41 << 2, x); } + function storeCst2(x) { x=c(x); return s2(u8, 41 << 2, x); } + + return { + load1: load1, + load2: load2, + loadCst1: loadCst1, + loadCst2: loadCst2, + store1: store1, + store2: store2, + storeCst1: storeCst1, + storeCst2: storeCst2, + } +`; +} + +var SIZE = 0x10000; + +function TestPartialLoads(m, typedArray, x, y, z, w) { + // Fill array with predictable values + for (var i = 0; i < SIZE; i += 4) { + typedArray[i] = x(i); + typedArray[i + 1] = y(i); + typedArray[i + 2] = z(i); + typedArray[i + 3] = w(i); + } + + // Test correct loads + var i = 0, j = 0; // i in elems, j in bytes + assertEqX4(m.load1(j), [x(i), 0, 0, 0]); + assertEqX4(m.load2(j), [x(i), y(i), 0, 0]); + + j += 4; + assertEqX4(m.load1(j), [y(i), 0, 0, 0]); + assertEqX4(m.load2(j), [y(i), z(i), 0, 0]); + + j += 4; + assertEqX4(m.load1(j), [z(i), 0, 0, 0]); + assertEqX4(m.load2(j), [z(i), w(i), 0, 0]); + + j += 4; + assertEqX4(m.load1(j), [w(i), 0, 0, 0]); + assertEqX4(m.load2(j), [w(i), x(i+4), 0, 0]); + + j += 4; + i += 4; + assertEqX4(m.load1(j), [x(i), 0, 0, 0]); + assertEqX4(m.load2(j), [x(i), y(i), 0, 0]); + + // Test loads with constant indexes (41) + assertEqX4(m.loadCst1(), [y(40), 0, 0, 0]); + assertEqX4(m.loadCst2(), [y(40), z(40), 0, 0]); + + // Test limit and OOB accesses + assertEqX4(m.load1((SIZE - 1) << 2), [w(SIZE - 4), 0, 0, 0]); + assertThrowsInstanceOf(() => m.load1(((SIZE - 1) << 2) + 1), RuntimeError); + + assertEqX4(m.load2((SIZE - 2) << 2), [z(SIZE - 4), w(SIZE - 4), 0, 0]); + assertThrowsInstanceOf(() => m.load2(((SIZE - 2) << 2) + 1), RuntimeError); +} + +// Partial stores +function TestPartialStores(m, typedArray, typeName, x, y, z, w) { + var val = SIMD[typeName](x, y, z, w); + + function Reset() { + for (var i = 0; i < SIZE; i++) + typedArray[i] = i + 1; + } + function CheckNotModified(low, high) { + for (var i = low; i < high; i++) + assertEq(typedArray[i], i + 1); + } + + function TestStore1(i) { + m.store1(i, val); + CheckNotModified(0, i >> 2); + assertEq(typedArray[i >> 2], x); + CheckNotModified((i >> 2) + 1, SIZE); + typedArray[i >> 2] = (i >> 2) + 1; + } + + function TestStore2(i) { + m.store2(i, val); + CheckNotModified(0, i >> 2); + assertEq(typedArray[i >> 2], x); + assertEq(typedArray[(i >> 2) + 1], y); + CheckNotModified((i >> 2) + 2, SIZE); + typedArray[i >> 2] = (i >> 2) + 1; + typedArray[(i >> 2) + 1] = (i >> 2) + 2; + } + + function TestOOBStore(f) { + assertThrowsInstanceOf(f, RuntimeError); + CheckNotModified(0, SIZE); + } + + Reset(); + + TestStore1(0); + TestStore1(1 << 2); + TestStore1(2 << 2); + TestStore1(3 << 2); + TestStore1(1337 << 2); + + var i = (SIZE - 1) << 2; + TestStore1(i); + TestOOBStore(() => m.store1(i + 1, val)); + TestOOBStore(() => m.store1(-1, val)); + + TestStore2(0); + TestStore2(1 << 2); + TestStore2(2 << 2); + TestStore2(3 << 2); + TestStore2(1337 << 2); + + var i = (SIZE - 2) << 2; + TestStore2(i); + TestOOBStore(() => m.store2(i + 1, val)); + TestOOBStore(() => m.store2(-1, val)); + + // Constant indexes (41) + m.storeCst1(val); + CheckNotModified(0, 41); + assertEq(typedArray[41], x); + CheckNotModified(42, SIZE); + typedArray[41] = 42; + + m.storeCst2(val); + CheckNotModified(0, 41); + assertEq(typedArray[41], x); + assertEq(typedArray[42], y); + CheckNotModified(43, SIZE); + typedArray[41] = 42; + typedArray[42] = 43; +} + +var f32 = new Float32Array(SIZE); +var mFloat32x4 = asmLink(asmCompile('glob', 'ffi', 'heap', MakeCodeFor('Float32x4')), this, null, f32.buffer); + +TestPartialLoads(mFloat32x4, f32, + (i) => i + 1, + (i) => Math.fround(13.37), + (i) => Math.fround(1/i), + (i) => Math.fround(Math.sqrt(0x2000 - i))); + +TestPartialStores(mFloat32x4, f32, 'Float32x4', 42, -0, NaN, 0.1337); + +var i32 = new Int32Array(f32.buffer); +var mInt32x4 = asmLink(asmCompile('glob', 'ffi', 'heap', MakeCodeFor('Int32x4')), this, null, i32.buffer); + +TestPartialLoads(mInt32x4, i32, + (i) => i + 1 | 0, + (i) => -i | 0, + (i) => i * 2 | 0, + (i) => 42); + +TestPartialStores(mInt32x4, i32, 'Int32x4', 42, -3, 13, 37); + +})(); + +} catch (e) { print('stack: ', e.stack); throw e } diff --git a/js/src/jit-test/tests/asm.js/testSIMD.js b/js/src/jit-test/tests/asm.js/testSIMD.js new file mode 100644 index 000000000000..29786bc52c9a --- /dev/null +++ b/js/src/jit-test/tests/asm.js/testSIMD.js @@ -0,0 +1,1575 @@ +load(libdir + "asm.js"); +load(libdir + "simd.js"); +load(libdir + "asserts.js"); +var heap = new ArrayBuffer(0x10000); + +// Avoid pathological --ion-eager compile times due to bails in loops +setJitCompilerOption('ion.warmup.trigger', 1000000); + +// Set to true to see more JS debugging spew +const DEBUG = false; + +if (!isSimdAvailable() || typeof SIMD === 'undefined' || !isAsmJSCompilationAvailable()) { + DEBUG && print("won't run tests as simd extensions aren't activated yet"); + quit(0); +} + +const I32 = 'var i4 = glob.SIMD.Int32x4;' +const CI32 = 'var ci4 = i4.check;' +const I32A = 'var i4a = i4.add;' +const I32S = 'var i4s = i4.sub;' +const I32M = 'var i4m = i4.mul;' +const I32U32 = 'var i4u4 = i4.fromUint32x4Bits;' + +const U32 = 'var u4 = glob.SIMD.Uint32x4;' +const CU32 = 'var cu4 = u4.check;' +const U32A = 'var u4a = u4.add;' +const U32S = 'var u4s = u4.sub;' +const U32M = 'var u4m = u4.mul;' +const U32I32 = 'var u4i4 = u4.fromInt32x4Bits;' + +const F32 = 'var f4 = glob.SIMD.Float32x4;' +const CF32 = 'var cf4 = f4.check;' +const F32A = 'var f4a = f4.add;' +const F32S = 'var f4s = f4.sub;' +const F32M = 'var f4m = f4.mul;' +const F32D = 'var f4d = f4.div;' +const FROUND = 'var f32=glob.Math.fround;' +const B32 = 'var b4 = glob.SIMD.Bool32x4;' +const CB32 = 'var cb4 = b4.check;' + +const EXTI4 = 'var e = i4.extractLane;' +const EXTU4 = 'var e = u4.extractLane;' +const EXTF4 = 'var e = f4.extractLane;' +const EXTB4 = 'var e = b4.extractLane;' + +// anyTrue / allTrue on boolean vectors. +const ANYB4 = 'var anyt=b4.anyTrue;' +const ALLB4 = 'var allt=b4.allTrue;' + +const INT32_MAX = Math.pow(2, 31) - 1; +const INT32_MIN = INT32_MAX + 1 | 0; +const UINT32_MAX = Math.pow(2, 32) - 1; + +const assertEqFFI = {assertEq:assertEq}; + +function CheckI4(header, code, expected) { + // code needs to contain a local called x + header = USE_ASM + I32 + CI32 + EXTI4 + F32 + header; + var observed = asmLink(asmCompile('glob', header + ';function f() {' + code + ';return ci4(x)} return f'), this)(); + assertEqX4(observed, expected); +} + +function CheckU4(header, code, expected) { + // code needs to contain a local called x. + header = USE_ASM + U32 + CU32 + EXTU4 + I32 + CI32 + I32U32 + header; + var observed = asmLink(asmCompile('glob', header + ';function f() {' + code + ';return ci4(i4u4(x))} return f'), this)(); + // We can't return an unsigned SIMD type. Return Int32x4, convert to unsigned here. + observed = SIMD.Uint32x4.fromInt32x4Bits(observed) + assertEqX4(observed, expected); +} + +function CheckF4(header, code, expected) { + // code needs to contain a local called x + header = USE_ASM + F32 + CF32 + EXTF4 + header; + var observed = asmLink(asmCompile('glob', header + ';function f() {' + code + ';return cf4(x)} return f'), this)(); + assertEqX4(observed, expected.map(Math.fround)); +} + +function CheckB4(header, code, expected) { + // code needs to contain a local called x + header = USE_ASM + B32 + CB32 + header; + var observed = asmLink(asmCompile('glob', header + ';function f() {' + code + ';return cb4(x)} return f'), this)(); + assertEqX4(observed, expected); +} + +try { + +// 1. Constructors + +// 1.1 Compilation +assertAsmTypeFail('glob', USE_ASM + "var i4 = Int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.Int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.globglob.Int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var i4 = glob.Math.Int32x4 ; return {}") ; +assertAsmTypeFail('glob', USE_ASM + "var herd = glob.SIMD.ponyX4 ; return {}") ; + +// 1.2 Linking +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: 42}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: Math.fround}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: 42}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: Math.fround}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: new Array}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: SIMD.Float32x4}}); + +var [Type, int32] = [TypedObject.StructType, TypedObject.int32]; +var MyStruct = new Type({'x': int32, 'y': int32, 'z': int32, 'w': int32}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: MyStruct}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + I32 + "return {}"), {SIMD: {Int32x4: new MyStruct}}); + +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {} return f"), {SIMD:{Int32x4: SIMD.Int32x4}})(), undefined); + +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {Float32x4: 42}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {Float32x4: Math.fround}}); +assertAsmLinkFail(asmCompile('glob', USE_ASM + F32 + "return {}"), {SIMD: {Float32x4: new Array}}); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {} return f"), {SIMD:{Float32x4: SIMD.Float32x4}})(), undefined); + +// 1.3 Correctness +// 1.3.1 Local variables declarations +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=Int32x4(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2, 3);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2, 3, 4.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1, 2.0, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4a(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,2+2|0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3," + (INT32_MIN - 1) + ");} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {var x=i4(i4(1,2,3,4));} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3,4);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {var x=i4(1,2,3," + (INT32_MAX + 1) + ");} return f"), this)(), undefined); + +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4();} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,3.);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,f32(3.),4.);} return f"); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1.,2.,3.,4.);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3," + (INT32_MIN - 1) + ");} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3," + (INT32_MAX + 1) + ");} return f"), this)(), undefined); + +// Places where NumLit can creep in +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {i=i|0; var z=0; switch(i|0) {case i4(1,2,3,4): z=1; break; default: z=2; break;}} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {i=i|0; var z=0; return i * i4(1,2,3,4) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(i) {var x=i4(1,2,3,i4(4,5,6,7))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "function f(i) {var x=i4(1,2,3,f4(4,5,6,7))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "function f(i) {var x=f4(1,2,3,i4(4,5,6,7))} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {return +i4(1,2,3,4)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f() {return i4(1,2,3,4)|0} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + FROUND + "function f() {return f32(i4(1,2,3,4))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + "function f() {return cf4(i4(1,2,3,4))} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {return +f4(1,2,3,4)} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {return f4(1,2,3,4)|0} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + FROUND + "function f() {return f32(f4(1,2,3,4))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + F32 + "function f() {return ci4(f4(1,2,3,4))} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {return i4(1,2,3,4);} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f() {return ci4(i4(1,2,3,4));} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {return f4(1,2,3,4);} return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f() {return cf4(f4(1,2,3,4));} return f"), this)(), [1, 2, 3, 4]); + +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + "function f() {i4(1,2,3,4);} return f"), this)(), undefined); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + "function f() {f4(1,2,3,4);} return f"), this)(), undefined); + +// Int32x4 ctor should accept int? +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + I32 + CI32 + "var i32=new glob.Int32Array(heap); function f(i) {i=i|0; return ci4(i4(i32[i>>2], 2, 3, 4))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [0, 2, 3, 4]); +// Float32x4 ctor should accept floatish (i.e. float || float? || floatish) and doublit +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + F32 + CF32 + FROUND + "var h=new glob.Float32Array(heap); function f(i) {i=i|0; return cf4(f4(h[i>>2], f32(2), f32(3), f32(4)))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [NaN, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + FROUND + "function f(i) {i=i|0; return cf4(f4(f32(1) + f32(2), f32(2), f32(3), f32(4)))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [3, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + FROUND + "function f(i) {i=i|0; return cf4(f4(f32(1) + f32(2), 2.0, 3.0, 4.0))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [3, 2, 3, 4]); +// Bool32x4 ctor should accept int? +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + B32 + CB32 + "var i32=new glob.Int32Array(heap); function f(i) {i=i|0; return cb4(b4(i32[i>>2], 2, 0, 4))} return f"), this, {}, new ArrayBuffer(0x10000))(0x20000), [false, true, false, true]); + +// 1.3.2 Getters - Reading values out of lanes +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=1; return e(x,1) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=1; return e(x + x, 1) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=1.; return e(x, 1) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + EXTF4 + "var f32=glob.Math.fround;" + I32 + "function f() {var x=f32(1); return e(x, 1) | 0;} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return x.length|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=e(i4(1,2,3,4),1); return x|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return (e(x,0) > (1>>>0)) | 0;} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return e(x,-1) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return e(x,4) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return e(x,.5) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); return e(x,x) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTF4 + "function f() {var x=i4(1,2,3,4); return e(x,0) | 0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + EXTI4 + "function f() {var x=i4(1,2,3,4); var i=0; return e(x,i) | 0;} return f"); + +// The signMask property is no longer supported. Replaced by allTrue / anyTrue. +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=42; return x.signMask;} return f"); +assertAsmTypeFail('glob', USE_ASM + "function f() {var x=42.; return x.signMask;} return f"); +assertAsmTypeFail('glob', USE_ASM + FROUND + "function f() {var x=f32(42.); return x.signMask;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + 'function f() { var x=i4(1,2,3,4); return x.signMask | 0 } return f'); +assertAsmTypeFail('glob', USE_ASM + U32 + 'function f() { var x=u4(1,2,3,4); return x.signMask | 0 } return f'); +assertAsmTypeFail('glob', USE_ASM + F32 + FROUND + 'var Infinity = glob.Infinity; function f() { var x=f4(0,0,0,0); x=f4(f32(1), f32(-13.37), f32(42), f32(-Infinity)); return x.signMask | 0 } return f'); + +// Check lane extraction. +function CheckLanes(innerBody, type, expected) { + var coerceBefore, coerceAfter, extractLane; + + if (type === SIMD.Int32x4) { + coerceBefore = ''; + coerceAfter = '|0'; + extractLane = 'ei'; + } else if (type === SIMD.Uint32x4) { + // Coerce Uint32 lanes to double so they can be legally returned. + coerceBefore = '+'; + coerceAfter = ''; + extractLane = 'eu'; + } else if (type === SIMD.Float32x4) { + coerceBefore = '+'; + coerceAfter = ''; + extractLane = 'ef'; + expected = expected.map(Math.fround); + } else if (type === SIMD.Bool32x4) { + coerceBefore = ''; + coerceAfter = '|0'; + extractLane = 'eb'; + } else throw "unexpected type in CheckLanes"; + + for (var i = 0; i < 4; i++) { + var lane = i; + var laneCheckCode = `"use asm"; + var i4=glob.SIMD.Int32x4; + var u4=glob.SIMD.Uint32x4; + var f4=glob.SIMD.Float32x4; + var b4=glob.SIMD.Bool32x4; + var ei=i4.extractLane; + var eu=u4.extractLane; + var ef=f4.extractLane; + var eb=b4.extractLane; + function f() {${innerBody}; return ${coerceBefore}${extractLane}(x, ${lane})${coerceAfter} } + return f;`; + assertEq(asmLink(asmCompile('glob', laneCheckCode), this)(), expected[i]); + } +} +function CheckLanesI4(innerBody, expected) { return CheckLanes(innerBody, SIMD.Int32x4, expected); } +function CheckLanesU4(innerBody, expected) { return CheckLanes(innerBody, SIMD.Uint32x4, expected); } +function CheckLanesF4(innerBody, expected) { return CheckLanes(innerBody, SIMD.Float32x4, expected); } +function CheckLanesB4(innerBody, expected) { return CheckLanes(innerBody, SIMD.Bool32x4, expected); } + +CheckLanesI4('var x=i4(0,0,0,0);', [0,0,0,0]); +CheckLanesI4('var x=i4(1,2,3,4);', [1,2,3,4]); +CheckLanesI4('var x=i4(' + INT32_MIN + ',2,3,' + INT32_MAX + ')', [INT32_MIN,2,3,INT32_MAX]); +CheckLanesI4('var x=i4(1,2,3,4); var y=i4(5,6,7,8)', [1,2,3,4]); +CheckLanesI4('var a=1; var b=i4(9,8,7,6); var c=13.37; var x=i4(1,2,3,4); var y=i4(5,6,7,8)', [1,2,3,4]); +CheckLanesI4('var y=i4(5,6,7,8); var x=i4(1,2,3,4)', [1,2,3,4]); + +CheckLanesU4('var x=u4(0,0,0,0);', [0,0,0,0]); +CheckLanesU4('var x=u4(1,2,3,4000000000);', [1,2,3,4000000000]); +CheckLanesU4('var x=u4(' + INT32_MIN + ',2,3,' + UINT32_MAX + ')', [INT32_MIN>>>0,2,3,UINT32_MAX]); +CheckLanesU4('var x=u4(1,2,3,4); var y=u4(5,6,7,8)', [1,2,3,4]); +CheckLanesU4('var a=1; var b=u4(9,8,7,6); var c=13.37; var x=u4(1,2,3,4); var y=u4(5,6,7,8)', [1,2,3,4]); +CheckLanesU4('var y=u4(5,6,7,8); var x=u4(1,2,3,4)', [1,2,3,4]); + +CheckLanesF4('var x=f4(' + INT32_MAX + ', 2, 3, ' + INT32_MIN + ')', [INT32_MAX, 2, 3, INT32_MIN]); +CheckLanesF4('var x=f4(' + (INT32_MAX + 1) + ', 2, 3, 4)', [INT32_MAX + 1, 2, 3, 4]); +CheckLanesF4('var x=f4(1.3, 2.4, 3.5, 98.76)', [1.3, 2.4, 3.5, 98.76]); +CheckLanesF4('var x=f4(13.37, 2., 3., -0)', [13.37, 2, 3, -0]); + +CheckLanesB4('var x=b4(0,0,0,0);', [0,0,0,0]); +CheckLanesB4('var x=b4(0,1,0,0);', [0,1,0,0]); +CheckLanesB4('var x=b4(0,2,0,0);', [0,1,0,0]); +CheckLanesB4('var x=b4(-1,0,1,-1);', [1,0,1,1]); + +// 1.3.3. Variable assignments +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1.0, 2, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2.0, 3, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3.0, 4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3, 4.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4(1, 2, 3, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); var c=4.0; x=i4(1, 2, 3, +c);} return f"); + +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + I32 + "var i32=new glob.Int32Array(heap); function f() {var x=i4(1,2,3,4); i32[0] = x;} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + I32 + "var i32=new glob.Int32Array(heap); function f() {var x=i4(1,2,3,4); x = i32[0];} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + F32 + "var f32=new glob.Float32Array(heap); function f() {var x=f4(1,2,3,4); f32[0] = x;} return f"); +assertAsmTypeFail('glob', 'ffi', 'heap', USE_ASM + F32 + "var f32=new glob.Int32Array(heap); function f() {var x=f4(1,2,3,4); x = f32[0];} return f"); + +CheckI4('', 'var x=i4(1,2,3,4); x=i4(5,6,7,8)', [5, 6, 7, 8]); +CheckI4('', 'var x=i4(1,2,3,4); var c=6; x=i4(5,c|0,7,8)', [5, 6, 7, 8]); +CheckI4('', 'var x=i4(8,7,6,5); x=i4(e(x,3)|0,e(x,2)|0,e(x,1)|0,e(x,0)|0)', [5, 6, 7, 8]); + +CheckU4('', 'var x=u4(1,2,3,4); x=u4(5,6,7,4000000000)', [5, 6, 7, 4000000000]); +CheckU4('', 'var x=u4(1,2,3,4); var c=6; x=u4(5,c|0,7,8)', [5, 6, 7, 8]); +CheckU4('', 'var x=u4(8,7,6,5); x=u4(e(x,3)|0,e(x,2)|0,e(x,1)|0,e(x,0)|0)', [5, 6, 7, 8]); + +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4; x=f4(1,2,3,c);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4; x=f4(1.,2.,3.,c);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4.; x=f4(1,2,3,c);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4.; x=f4(1.,2.,3.,c);} return f"); + +CheckF4(FROUND, 'var x=f4(1,2,3,4); var y=f32(7.); x=f4(f32(5),f32(6),y,f32(8))', [5, 6, 7, 8]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); x=f4(f32(5),f32(6),f32(7),f32(8))', [5, 6, 7, 8]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); x=f4(f32(5.),f32(6.),f32(7.),f32(8.))', [5, 6, 7, 8]); +CheckF4('', 'var x=f4(1.,2.,3.,4.); x=f4(5.,6.,7.,8.)', [5, 6, 7, 8]); +CheckF4('', 'var x=f4(1.,2.,3.,4.); x=f4(1,2,3,4)', [1, 2, 3, 4]); +CheckF4(FROUND, 'var x=f4(1.,2.,3.,4.); var y=f32(7.); x=f4(9, 4, 2, 1)', [9, 4, 2, 1]); +CheckF4('', 'var x=f4(8.,7.,6.,5.); x=f4(e(x,3),e(x,2),e(x,1),e(x,0))', [5, 6, 7, 8]); + +// Optimization for all lanes from the same definition. +CheckI4('', 'var x=i4(1,2,3,4); var c=6; x=i4(c|0,c|0,c|0,c|0)', [6, 6, 6, 6]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); var y=f32(7.); x=f4(y,y,y,y)', [7, 7, 7, 7]); +CheckI4('', 'var x=i4(1,2,3,4); var c=0; c=e(x,3)|0; x=i4(c,c,c,c)', [4, 4, 4, 4]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); var y=f32(0); y=e(x,2); x=f4(y,y,y,y)', [3, 3, 3, 3]); +CheckI4('', 'var x=i4(1,2,3,4); var c=0; var d=0; c=e(x,3)|0; d=e(x,3)|0; x=i4(c,d,d,c)', [4, 4, 4, 4]); +CheckF4(FROUND, 'var x=f4(1,2,3,4); var y=f32(0); var z=f32(0); y=e(x,2); z=e(x,2); x=f4(y,z,y,z)', [3, 3, 3, 3]); + +// Uses in ternary conditionals +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4; c=x?c:c;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4; x=1?x:c;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "function f() {var x=f4(1,2,3,4); var c=4; x=1?c:x;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + "function f() {var x=f4(1,2,3,4); var y=i4(1,2,3,4); x=1?x:y;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + "function f() {var x=f4(1,2,3,4); var y=i4(1,2,3,4); x=1?y:y;} return f"); +assertAsmTypeFail('glob', USE_ASM + B32 + I32 + "function f() {var x=b4(1,2,3,4); var y=i4(1,2,3,4); x=1?y:y;} return f"); +assertAsmTypeFail('glob', USE_ASM + U32 + I32 + "function f() {var x=u4(1,2,3,4); var y=i4(1,2,3,4); x=1?y:y;} return f"); +assertAsmTypeFail('glob', USE_ASM + U32 + I32 + "function f() {var x=i4(1,2,3,4); var y=u4(1,2,3,4); x=1?y:y;} return f"); + +CheckF4('', 'var x=f4(1,2,3,4); var y=f4(4,3,2,1); x=3?y:x', [4, 3, 2, 1]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(x) {x=x|0; var v=f4(1,2,3,4); var w=f4(5,6,7,8); return cf4(x?w:v);} return f"), this)(1), [5,6,7,8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(v) {v=cf4(v); var w=f4(5,6,7,8); return cf4(4?w:v);} return f"), this)(SIMD.Float32x4(1,2,3,4)), [5,6,7,8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(v, x) {v=cf4(v); x=x|0; var w=f4(5,6,7,8); return cf4(x?w:v);} return f"), this)(SIMD.Float32x4(1,2,3,4), 0), [1,2,3,4]); + +CheckI4('', 'var x=i4(1,2,3,4); var y=i4(4,3,2,1); x=e(x,0)?y:x', [4, 3, 2, 1]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(x) {x=x|0; var v=i4(1,2,3,4); var w=i4(5,6,7,8); return ci4(x?w:v);} return f"), this)(1), [5,6,7,8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(v) {v=ci4(v); var w=i4(5,6,7,8); return ci4(4?w:v);} return f"), this)(SIMD.Int32x4(1,2,3,4)), [5,6,7,8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(v, x) {v=ci4(v); x=x|0; var w=i4(5,6,7,8); return ci4(x?w:v);} return f"), this)(SIMD.Int32x4(1,2,3,4), 0), [1,2,3,4]); + +// Unsigned SIMD types can't be function arguments or return values. +assertAsmTypeFail('glob', USE_ASM + U32 + CU32 + "function f(x) {x=cu4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + U32 + CU32 + "function f() {x=u4(0,0,0,0); return cu4(x);} return f"); + +// 1.3.4 Return values +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {var x=1; return ci4(x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {var x=1; return ci4(x + x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {var x=1.; return ci4(x)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + FROUND + "function f() {var x=f32(1.); return ci4(x)} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f() {var x=i4(1,2,3,4); return ci4(x)} return f"), this)(), [1,2,3,4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f() {var x=f4(1,2,3,4); return cf4(x)} return f"), this)(), [1,2,3,4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + "function f() {var x=b4(1,2,0,4); return cb4(x)} return f"), this)(), [true,true,false,true]); + +// 1.3.5 Coerce and pass arguments +// Via check +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {ci4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x); ci4(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {ci4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f() {ci4(1.);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + FROUND + "function f() {ci4(f32(1.));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + F32 + CF32 + "function f(x) {x=cf4(x); ci4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x); return 1 + ci4(x) | 0;} return f"); + +var i32x4 = SIMD.Int32x4(1, 3, 3, 7); +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x)} return f"), this)(i32x4), undefined); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x); return ci4(x);} return f"), this)(i32x4), [1,3,3,7]); + +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f() {cf4();} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x); cf4(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f() {cf4(1);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f() {cf4(1.);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + FROUND + "function f() {cf4(f32(1.));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + F32 + CF32 + "function f(x) {x=cf4(x); cf4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x); return 1 + cf4(x) | 0;} return f"); + +var f32x4 = SIMD.Float32x4(13.37, 42.42, -0, NaN); +assertEq(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x)} return f"), this)(f32x4), undefined); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x); return cf4(x);} return f"), this)(f32x4), [13.37, 42.42, -0, NaN].map(Math.fround)); + +var b32x4 = SIMD.Bool32x4(true, false, false, true); +assertEq(asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + "function f(x) {x=cb4(x)} return f"), this)(b32x4), undefined); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + "function f(x) {x=cb4(x); return cb4(x);} return f"), this)(b32x4), [true, false, false, true]); + +// Legacy coercions +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {x=i4();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {x=i4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {x=i4(1,2,3,4);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x,y) {x=i4(y);y=+y} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x); return i4(x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {return +i4(1,2,3,4)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "function f(x) {return 0|i4(1,2,3,4)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + FROUND + "function f(x) {return f32(i4(1,2,3,4))} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "function f(x) {return f4(i4(1,2,3,4))} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x); return f4(x);} return f"); + + +function assertCaught(f) { + var caught = false; + try { + f.apply(null, Array.prototype.slice.call(arguments, 1)); + } catch (e) { + DEBUG && print('Assert caught: ', e, '\n', e.stack); + assertEq(e instanceof TypeError, true); + caught = true; + } + assertEq(caught, true); +} + +var f = asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + "function f(x) {x=cf4(x); return cf4(x);} return f"), this); +assertCaught(f); +assertCaught(f, 1); +assertCaught(f, {}); +assertCaught(f, "I sincerely am a SIMD typed object."); +assertCaught(f, SIMD.Int32x4(1,2,3,4)); +assertCaught(f, SIMD.Bool32x4(true, true, false, true)); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + "function f(x) {x=ci4(x); return ci4(x);} return f"), this); +assertCaught(f); +assertCaught(f, 1); +assertCaught(f, {}); +assertCaught(f, "I sincerely am a SIMD typed object."); +assertCaught(f, SIMD.Float32x4(4,3,2,1)); +assertCaught(f, SIMD.Bool32x4(true, true, false, true)); + +var f = asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + "function f(x) {x=cb4(x); return cb4(x);} return f"), this); +assertCaught(f); +assertCaught(f, 1); +assertCaught(f, {}); +assertCaught(f, "I sincerely am a SIMD typed object."); +assertCaught(f, SIMD.Int32x4(1,2,3,4)); +assertCaught(f, SIMD.Float32x4(4,3,2,1)); + +// 1.3.6 Globals +// 1.3.6.1 Local globals +// Read +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4; x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4.; x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); var f32=glob.Math.fround; function f() {var x=f32(4.); x=f32(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4; x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4.; x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); var f32=glob.Math.fround; function f() {var x=f32(4.); x=f32(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + CI32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); x=ci4(g);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); x=cf4(g);} return f"); +assertAsmTypeFail('glob', USE_ASM + U32 + I32 + CI32 + "var g=u4(1,2,3,4); function f() {var x=i4(1,2,3,4); x=ci4(g);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=0; function f() {var x=i4(1,2,3,4); x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=0.; function f() {var x=i4(1,2,3,4); x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var f32=glob.Math.fround; var g=f32(0.); function f() {var x=i4(1,2,3,4); x=f32(g);} return f"); + +// Unsigned SIMD globals are not allowed. +assertAsmTypeFail('glob', USE_ASM + U32 + "var g=u4(0,0,0,0); function f() {var x=u4(1,2,3,4); x=g;} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=0; function f() {var x=f4(0.,0.,0.,0.); x=g|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=0.; function f() {var x=f4(0.,0.,0.,0.); x=+g;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var f32=glob.Math.fround; var g=f32(0.); function f() {var x=f4(0.,0.,0.,0.); x=f32(g);} return f"); + +CheckI4('var x=i4(1,2,3,4)', '', [1, 2, 3, 4]); +CheckI4('var _=42; var h=i4(5,5,5,5); var __=13.37; var x=i4(4,7,9,2);', '', [4,7,9,2]); + +CheckF4('var x=f4(1.,2.,3.,4.)', '', [1, 2, 3, 4]); +CheckF4('var _=42; var h=f4(5.,5.,5.,5.); var __=13.37; var x=f4(4.,13.37,9.,-0.);', '', [4, 13.37, 9, -0]); +CheckF4('var x=f4(1,2,3,4)', '', [1, 2, 3, 4]); + +CheckB4('var x=b4(1,0,3,0)', '', [true, false, true, false]); +CheckB4('var _=42; var h=b4(5,0,5,5); var __=13.37; var x=b4(0,0,9,2);', '', [false, false, true, true]); + +// Write +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4; g=x|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); function f() {var x=4.; g=+x;} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var g=i4(1,2,3,4); var f32=glob.Math.fround; function f() {var x=f32(4.); g=f32(x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4; g=x|0;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); function f() {var x=4.; g=+x;} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var g=f4(1., 2., 3., 4.); var f32=glob.Math.fround; function f() {var x=f32(4.); g=f32(x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + CI32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); g=ci4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + I32 + CF32 + "var g=f4(1., 2., 3., 4.); function f() {var x=i4(1,2,3,4); g=cf4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); g=cf4(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CI32 + "var g=i4(1,2,3,4); function f() {var x=f4(1.,2.,3.,4.); g=ci4(x);} return f"); + +CheckI4('var x=i4(0,0,0,0);', 'x=i4(1,2,3,4)', [1,2,3,4]); +CheckF4('var x=f4(0.,0.,0.,0.);', 'x=f4(5.,3.,4.,2.)', [5,3,4,2]); +CheckB4('var x=b4(0,0,0,0);', 'x=b4(0,0,1,1)', [false, false, true, true]); + +CheckI4('var x=i4(0,0,0,0); var y=42; var z=3.9; var w=13.37', 'x=i4(1,2,3,4); y=24; z=4.9; w=23.10;', [1,2,3,4]); +CheckF4('var x=f4(0,0,0,0); var y=42; var z=3.9; var w=13.37', 'x=f4(1,2,3,4); y=24; z=4.9; w=23.10;', [1,2,3,4]); +CheckB4('var x=b4(0,0,0,0); var y=42; var z=3.9; var w=13.37', 'x=b4(1,0,0,0); y=24; z=4.9; w=23.10;', [true, false, false, false]); + +// 1.3.6.2 Imported globals +// Read +var Int32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + I32 + CI32 + "var g=ci4(ffi.g); function f() {return ci4(g)} return f"), this, {g: SIMD.Int32x4(1,2,3,4)})(); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 0), 1); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 1), 2); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 2), 3); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 3), 4); + +for (var v of [1, {}, "totally legit SIMD variable", SIMD.Float32x4(1,2,3,4)]) + assertCaught(asmCompile('glob', 'ffi', USE_ASM + I32 + CI32 + "var g=ci4(ffi.g); function f() {return ci4(g)} return f"), this, {g: v}); + +// Unsigned SIMD globals are not allowed. +assertAsmTypeFail('glob', 'ffi', USE_ASM + U32 + CU32 + "var g=cu4(ffi.g); function f() {} return f"); + +var Float32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + F32 + CF32 + "var g=cf4(ffi.g); function f() {return cf4(g)} return f"), this, {g: SIMD.Float32x4(1,2,3,4)})(); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 0), 1); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 1), 2); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 2), 3); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 3), 4); + +for (var v of [1, {}, "totally legit SIMD variable", SIMD.Int32x4(1,2,3,4)]) + assertCaught(asmCompile('glob', 'ffi', USE_ASM + F32 + CF32 + "var g=cf4(ffi.g); function f() {return cf4(g)} return f"), this, {g: v}); + +var Bool32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + "var g=cb4(ffi.g); function f() {return cb4(g)} return f"), this, {g: SIMD.Bool32x4(false, false, false, true)})(); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 0), false); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 1), false); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 2), false); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 3), true); + +for (var v of [1, {}, "totally legit SIMD variable", SIMD.Int32x4(1,2,3,4)]) + assertCaught(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + "var g=cb4(ffi.g); function f() {return cb4(g)} return f"), this, {g: v}); + +// Write +var Int32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + I32 + CI32 + "var g=ci4(ffi.g); function f() {g=i4(4,5,6,7); return ci4(g)} return f"), this, {g: SIMD.Int32x4(1,2,3,4)})(); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 0), 4); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 1), 5); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 2), 6); +assertEq(SIMD.Int32x4.extractLane(Int32x4, 3), 7); + +var Float32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + F32 + CF32 + "var g=cf4(ffi.g); function f() {g=f4(4.,5.,6.,7.); return cf4(g)} return f"), this, {g: SIMD.Float32x4(1,2,3,4)})(); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 0), 4); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 1), 5); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 2), 6); +assertEq(SIMD.Float32x4.extractLane(Float32x4, 3), 7); + +var Bool32x4 = asmLink(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + "var g=cb4(ffi.g); function f() {g=b4(1,1,0,0); return cb4(g)} return f"), this, {g: SIMD.Bool32x4(1,1,1,0)})(); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 0), true); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 1), true); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 2), false); +assertEq(SIMD.Bool32x4.extractLane(Bool32x4, 3), false); + +// 2. SIMD operations +// 2.1 Compilation +assertAsmTypeFail('glob', USE_ASM + "var add = Int32x4.add; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32A + I32 + "return {}"); +assertAsmTypeFail('glob', USE_ASM + "var g = 3; var add = g.add; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var func = i4.doTheHarlemShake; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var div = i4.div; return {}"); +assertAsmTypeFail('glob', USE_ASM + "var f32 = glob.Math.fround; var i4a = f32.add; return {}"); +// Operation exists, but in a different type. +assertAsmTypeFail('glob', USE_ASM + I32 + "var func = i4.fromUint32x4; return {}"); + +// 2.2 Linking +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {}); +assertAsmLinkAlwaysFail(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {SIMD: Math.fround}); + +var oldInt32x4Add = SIMD.Int32x4.add; +var code = asmCompile('glob', USE_ASM + I32 + I32A + "return {}"); +for (var v of [42, Math.fround, SIMD.Float32x4.add, function(){}, SIMD.Int32x4.mul]) { + SIMD.Int32x4.add = v; + assertAsmLinkFail(code, {SIMD: {Int32x4: SIMD.Int32x4}}); +} +SIMD.Int32x4.add = oldInt32x4Add; // finally replace the add function with the original one +assertEq(asmLink(asmCompile('glob', USE_ASM + I32 + I32A + "function f() {} return f"), {SIMD: {Int32x4: SIMD.Int32x4}})(), undefined); + +// 2.3. Binary arithmetic operations +// 2.3.1 Additions +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a();} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(13, 37);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(23.10, 19.89);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, 42);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); x=i4a(x, 13.37);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(1,2,3,4); var y=4; x=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(0,0,0,0); var y=4; x=i4a(y, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + "function f() {var x=i4(0,0,0,0); var y=4; y=i4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + U32 + "function f() {var x=i4(0,0,0,0); var y=u4(1,2,3,4); y=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); x=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=i4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=i4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=f4a(x, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); y=f4a(x, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + F32A + "function f() {var x=i4(0,0,0,0); var y=f4(4,3,2,1); x=f4a(y, y);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + 'function f() {var x=i4(1,2,3,4); var y=0; y=i4a(x,x)|0} return f'); +assertAsmTypeFail('glob', USE_ASM + I32 + I32A + 'function f() {var x=i4(1,2,3,4); var y=0.; y=+i4a(x,x)} return f'); + +CheckI4(I32A, 'var z=i4(1,2,3,4); var y=i4(0,1,0,3); var x=i4(0,0,0,0); x=i4a(z,y)', [1,3,3,7]); +CheckI4(I32A, 'var x=i4(2,3,4,5); var y=i4(0,1,0,3); x=i4a(x,y)', [2,4,4,8]); +CheckI4(I32A, 'var x=i4(1,2,3,4); x=i4a(x,x)', [2,4,6,8]); +CheckI4(I32A, 'var x=i4(' + INT32_MAX + ',2,3,4); var y=i4(1,1,0,3); x=i4a(x,y)', [INT32_MIN,3,3,7]); +CheckI4(I32A, 'var x=i4(' + INT32_MAX + ',2,3,4); var y=i4(1,1,0,3); x=ci4(i4a(x,y))', [INT32_MIN,3,3,7]); + +CheckU4(U32A, 'var z=u4(1,2,3,4); var y=u4(0,1,0,3); var x=u4(0,0,0,0); x=u4a(z,y)', [1,3,3,7]); +CheckU4(U32A, 'var x=u4(2,3,4,5); var y=u4(0,1,0,3); x=u4a(x,y)', [2,4,4,8]); +CheckU4(U32A, 'var x=u4(1,2,3,4); x=u4a(x,x)', [2,4,6,8]); + +CheckF4(F32A, 'var x=f4(1,2,3,4); x=f4a(x,x)', [2,4,6,8]); +CheckF4(F32A, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4a(x,y)', [5,5,8,6]); +CheckF4(F32A, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4a(x,y)', [Math.fround(13.37) + 4,5,8,6]); +CheckF4(F32A, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=cf4(f4a(x,y))', [Math.fround(13.37) + 4,5,8,6]); + +// 2.3.2. Subtracts +CheckI4(I32S, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=i4s(x,y)', [2,1,3,2]); +CheckI4(I32S, 'var x=i4(5,4,3,2); var y=i4(1,2,3,4); x=i4s(x,y)', [4,2,0,-2]); +CheckI4(I32S, 'var x=i4(1,2,3,4); x=i4s(x,x)', [0,0,0,0]); +CheckI4(I32S, 'var x=i4(' + INT32_MIN + ',2,3,4); var y=i4(1,1,0,3); x=i4s(x,y)', [INT32_MAX,1,3,1]); +CheckI4(I32S, 'var x=i4(' + INT32_MIN + ',2,3,4); var y=i4(1,1,0,3); x=ci4(i4s(x,y))', [INT32_MAX,1,3,1]); + +CheckU4(U32S, 'var x=u4(1,2,3,4); var y=u4(-1,1,0,2); x=u4s(x,y)', [2,1,3,2]); +CheckU4(U32S, 'var x=u4(5,4,3,2); var y=u4(1,2,3,4); x=u4s(x,y)', [4,2,0,-2>>>0]); +CheckU4(U32S, 'var x=u4(1,2,3,4); x=u4s(x,x)', [0,0,0,0]); +CheckU4(U32S, 'var x=u4(' + INT32_MIN + ',2,3,4); var y=u4(1,1,0,3); x=u4s(x,y)', [INT32_MAX,1,3,1]); +CheckU4(U32S, 'var x=u4(' + INT32_MIN + ',2,3,4); var y=u4(1,1,0,3); x=cu4(u4s(x,y))', [INT32_MAX,1,3,1]); + +CheckF4(F32S, 'var x=f4(1,2,3,4); x=f4s(x,x)', [0,0,0,0]); +CheckF4(F32S, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4s(x,y)', [-3,-1,-2,2]); +CheckF4(F32S, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4s(x,y)', [Math.fround(13.37) - 4,-1,-2,2]); +CheckF4(F32S, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=cf4(f4s(x,y))', [Math.fround(13.37) - 4,-1,-2,2]); + +{ + // Bug 1216099 + let code = ` + "use asm"; + var f4 = global.SIMD.Float32x4; + var f4sub = f4.sub; + const zerox4 = f4(0.0, 0.0, 0.0, 0.0); + function f() { + var newVelx4 = f4(0.0, 0.0, 0.0, 0.0); + var newVelTruex4 = f4(0.0,0.0,0.0,0.0); + newVelTruex4 = f4sub(zerox4, newVelx4); + } + // return statement voluntarily missing + `; + assertAsmTypeFail('global', code); +} + +// 2.3.3. Multiplications / Divisions +assertAsmTypeFail('glob', USE_ASM + I32 + "var f4d=i4.div; function f() {} return f"); + +CheckI4(I32M, 'var x=i4(1,2,3,4); var y=i4(-1,1,0,2); x=i4m(x,y)', [-1,2,0,8]); +CheckI4(I32M, 'var x=i4(5,4,3,2); var y=i4(1,2,3,4); x=i4m(x,y)', [5,8,9,8]); +CheckI4(I32M, 'var x=i4(1,2,3,4); x=i4m(x,x)', [1,4,9,16]); +(function() { + var m = INT32_MIN, M = INT32_MAX, imul = Math.imul; + CheckI4(I32M, `var x=i4(${m},${m}, ${M}, ${M}); var y=i4(2,-3,4,-5); x=i4m(x,y)`, + [imul(m, 2), imul(m, -3), imul(M, 4), imul(M, -5)]); + CheckI4(I32M, `var x=i4(${m},${m}, ${M}, ${M}); var y=i4(${m}, ${M}, ${m}, ${M}); x=i4m(x,y)`, + [imul(m, m), imul(m, M), imul(M, m), imul(M, M)]); +})(); + +CheckF4(F32M, 'var x=f4(1,2,3,4); x=f4m(x,x)', [1,4,9,16]); +CheckF4(F32M, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [4,6,15,8]); +CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=f4m(x,y)', [Math.fround(13.37) * 4,6,15,8]); +CheckF4(F32M, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=cf4(f4m(x,y))', [Math.fround(13.37) * 4,6,15,8]); + +var f32x4 = SIMD.Float32x4(0, NaN, -0, NaN); +var another = SIMD.Float32x4(NaN, -1, -0, NaN); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32M + CF32 + "function f(x, y) {x=cf4(x); y=cf4(y); x=f4m(x,y); return cf4(x);} return f"), this)(f32x4, another), [NaN, NaN, 0, NaN]); + +CheckF4(F32D, 'var x=f4(1,2,3,4); x=f4d(x,x)', [1,1,1,1]); +CheckF4(F32D, 'var x=f4(1,2,3,4); var y=f4(4,3,5,2); x=f4d(x,y)', [1/4,2/3,3/5,2]); +CheckF4(F32D, 'var x=f4(13.37,1,1,4); var y=f4(4,0,-0.,2); x=f4d(x,y)', [Math.fround(13.37) / 4,+Infinity,-Infinity,2]); + +var f32x4 = SIMD.Float32x4(0, 0, -0, NaN); +var another = SIMD.Float32x4(0, -0, 0, 0); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + F32D + CF32 + "function f(x,y) {x=cf4(x); y=cf4(y); x=f4d(x,y); return cf4(x);} return f"), this)(f32x4, another), [NaN, NaN, NaN, NaN]); + +// Unary arithmetic operators +function CheckUnaryF4(op, checkFunc, assertFunc) { + var _ = asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + 'var op=f4.' + op + '; function f(x){x=cf4(x); return cf4(op(x)); } return f'), this); + return function(input) { + var simd = SIMD.Float32x4(input[0], input[1], input[2], input[3]); + + var exp = input.map(Math.fround).map(checkFunc).map(Math.fround); + var obs = _(simd); + assertEqX4(obs, exp, assertFunc); + } +} + +function CheckUnaryI4(op, checkFunc) { + var _ = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + 'var op=i4.' + op + '; function f(x){x=ci4(x); return ci4(op(x)); } return f'), this); + return function(input) { + var simd = SIMD.Int32x4(input[0], input[1], input[2], input[3]); + assertEqX4(_(simd), input.map(checkFunc).map(function(x) { return x | 0})); + } +} + +function CheckUnaryU4(op, checkFunc) { + var _ = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + I32U32 + U32 + U32I32 + + 'var op=u4.' + op + '; function f(x){x=ci4(x); return ci4(i4u4(op(u4i4(x)))); } return f'), this); + return function(input) { + var simd = SIMD.Int32x4(input[0], input[1], input[2], input[3]); + var res = SIMD.Uint32x4.fromInt32x4Bits(_(simd)); + assertEqX4(res, input.map(checkFunc).map(function(x) { return x >>> 0 })); + } +} + +function CheckUnaryB4(op, checkFunc) { + var _ = asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + 'var op=b4.' + op + '; function f(x){x=cb4(x); return cb4(op(x)); } return f'), this); + return function(input) { + var simd = SIMD.Bool32x4(input[0], input[1], input[2], input[3]); + assertEqX4(_(simd), input.map(checkFunc).map(function(x) { return !!x})); + } +} + +CheckUnaryI4('neg', function(x) { return -x })([1, -2, INT32_MIN, INT32_MAX]); +CheckUnaryI4('not', function(x) { return ~x })([1, -2, INT32_MIN, INT32_MAX]); + +CheckUnaryU4('neg', function(x) { return -x })([1, -2, INT32_MIN, INT32_MAX]); +CheckUnaryU4('not', function(x) { return ~x })([1, -2, INT32_MIN, INT32_MAX]); + +var CheckNotB = CheckUnaryB4('not', function(x) { return !x }); +CheckNotB([true, false, true, true]); +CheckNotB([true, true, true, true]); +CheckNotB([false, false, false, false]); + +var CheckAbs = CheckUnaryF4('abs', Math.abs); +CheckAbs([1, 42.42, 0.63, 13.37]); +CheckAbs([NaN, -Infinity, Infinity, 0]); + +var CheckNegF = CheckUnaryF4('neg', function(x) { return -x }); +CheckNegF([1, 42.42, 0.63, 13.37]); +CheckNegF([NaN, -Infinity, Infinity, 0]); + +var CheckSqrt = CheckUnaryF4('sqrt', function(x) { return Math.sqrt(x); }); +CheckSqrt([1, 42.42, 0.63, 13.37]); +CheckSqrt([NaN, -Infinity, Infinity, 0]); + +// ReciprocalApproximation and reciprocalSqrtApproximation give approximate results +function assertNear(a, b) { + if (a !== a && b === b) + throw 'Observed NaN, expected ' + b; + if (Math.abs(a - b) > 1e-3) + throw 'More than 1e-3 between ' + a + ' and ' + b; +} +var CheckRecp = CheckUnaryF4('reciprocalApproximation', function(x) { return 1 / x; }, assertNear); +CheckRecp([1, 42.42, 0.63, 13.37]); +CheckRecp([NaN, -Infinity, Infinity, 0]); + +var CheckRecp = CheckUnaryF4('reciprocalSqrtApproximation', function(x) { return 1 / Math.sqrt(x); }, assertNear); +CheckRecp([1, 42.42, 0.63, 13.37]); +CheckRecp([NaN, -Infinity, Infinity, 0]); + +// Min/Max +assertAsmTypeFail('glob', USE_ASM + I32 + "var f4m=i4.min; function f() {} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var f4d=i4.max; function f() {} return f"); + +const F32MIN = 'var min = f4.min;' +const F32MAX = 'var max = f4.max;' + +CheckF4(F32MIN, 'var x=f4(1,2,3,4); x=min(x,x)', [1,2,3,4]); +CheckF4(F32MIN, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=min(x,y)', [4,2,3,2]); +CheckF4(F32MIN + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=min(x,y)', [2310,-Infinity,3,-0]); + +CheckF4(F32MIN, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=min(x,y)', [0,-0,-0,-0]); +CheckF4(F32MIN + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=min(x,y)', [NaN, NaN, NaN, 0]); + +CheckF4(F32MAX, 'var x=f4(1,2,3,4); x=max(x,x)', [1,2,3,4]); +CheckF4(F32MAX, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=max(x,y)', [13.37, 3, 5, 4]); +CheckF4(F32MAX + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=max(x,y)', [+Infinity,3,5,0]); + +CheckF4(F32MAX, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=max(x,y)', [0,0,0,-0]); +CheckF4(F32MAX + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=max(x,y)', [NaN, NaN, NaN, 0]); + +const F32MINNUM = 'var min = f4.minNum;' +const F32MAXNUM = 'var max = f4.maxNum;' + +CheckF4(F32MINNUM, 'var x=f4(1,2,3,4); x=min(x,x)', [1,2,3,4]); +CheckF4(F32MINNUM, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=min(x,y)', [4,2,3,2]); +CheckF4(F32MINNUM + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=min(x,y)', [2310,-Infinity,3,-0]); + +CheckF4(F32MINNUM, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=min(x,y)', [0,-0,-0,-0]); +CheckF4(F32MINNUM + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=min(x,y)', [NaN, 0, 0, 0]); + +CheckF4(F32MAXNUM, 'var x=f4(1,2,3,4); x=max(x,x)', [1,2,3,4]); +CheckF4(F32MAXNUM, 'var x=f4(13.37,2,3,4); var y=f4(4,3,5,2); x=max(x,y)', [13.37, 3, 5, 4]); +CheckF4(F32MAXNUM + FROUND + 'var Infinity = glob.Infinity;', 'var x=f4(0,0,0,0); var y=f4(2310,3,5,0); x=f4(f32(+Infinity),f32(-Infinity),f32(3),f32(-0.)); x=max(x,y)', [+Infinity,3,5,0]); + +CheckF4(F32MAXNUM, 'var x=f4(0,0,-0,-0); var y=f4(0,-0,0,-0); x=max(x,y)', [0,0,0,-0]); +CheckF4(F32MAXNUM + FROUND + 'var NaN = glob.NaN;', 'var x=f4(0,0,0,0); var y=f4(0,0,0,0); var n=f32(0); n=f32(NaN); x=f4(n,0.,n,0.); y=f4(n,n,0.,0.); x=max(x,y)', [NaN, 0, 0, 0]); + +// ReplaceLane +const RLF = 'var r = f4.replaceLane;'; + +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + "function f() {var x = f4(1,2,3,4); x = r(x, 0, 1);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + "function f() {var x = f4(1,2,3,4); x = r(x, 0, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(x, 4, f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(x, f32(0), f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(1, 0, f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(1, 0., f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); x = r(f32(1), 0, f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); var l = 0; x = r(x, l, f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + RLF + FROUND + "function f() {var x = f4(1,2,3,4); var y = i4(1,2,3,4); x = r(y, 0, f32(1));} return f"); + +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 0, f32(13.37));', [Math.fround(13.37), 2, 3, 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 1, f32(13.37));', [1, Math.fround(13.37), 3, 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 2, f32(13.37));', [1, 2, Math.fround(13.37), 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, f32(13.37));', [1, 2, 3, Math.fround(13.37)]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, f32(13.37) + f32(6.63));', [1, 2, 3, Math.fround(Math.fround(13.37) + Math.fround(6.63))]); + +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 0, 13.37);', [Math.fround(13.37), 2, 3, 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 1, 13.37);', [1, Math.fround(13.37), 3, 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 2, 13.37);', [1, 2, Math.fround(13.37), 4]); +CheckF4(RLF + FROUND, 'var x = f4(1,2,3,4); x = r(x, 3, 13.37);', [1, 2, 3, Math.fround(13.37)]); + +const RLI = 'var r = i4.replaceLane;'; +CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 0, 42);', [42, 2, 3, 4]); +CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 1, 42);', [1, 42, 3, 4]); +CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 2, 42);', [1, 2, 42, 4]); +CheckI4(RLI, 'var x = i4(1,2,3,4); x = r(x, 3, 42);', [1, 2, 3, 42]); + +const RLU = 'var r = u4.replaceLane;'; +CheckU4(RLU, 'var x = u4(1,2,3,4); x = r(x, 0, 42);', [42, 2, 3, 4]); +CheckU4(RLU, 'var x = u4(1,2,3,4); x = r(x, 1, 42);', [1, 42, 3, 4]); +CheckU4(RLU, 'var x = u4(1,2,3,4); x = r(x, 2, 42);', [1, 2, 42, 4]); +CheckU4(RLU, 'var x = u4(1,2,3,4); x = r(x, 3, 42);', [1, 2, 3, 42]); + +const RLB = 'var r = b4.replaceLane;'; +CheckB4(RLB, 'var x = b4(1,1,0,0); x = r(x, 0, 0);', [false, true, false, false]); +CheckB4(RLB, 'var x = b4(1,1,0,0); x = r(x, 1, 0);', [true, false, false, false]); +CheckB4(RLB, 'var x = b4(1,1,0,0); x = r(x, 2, 2);', [true, true, true, false]); +CheckB4(RLB, 'var x = b4(1,1,0,0); x = r(x, 3, 1);', [true, true, false, true]); + +// Comparisons +// Comparison operators produce Bool32x4 vectors. +const T = true; +const F = false; + +const EQI32 = 'var eq = i4.equal'; +const NEI32 = 'var ne = i4.notEqual'; +const LTI32 = 'var lt = i4.lessThan;'; +const LEI32 = 'var le = i4.lessThanOrEqual'; +const GTI32 = 'var gt = i4.greaterThan;'; +const GEI32 = 'var ge = i4.greaterThanOrEqual'; + +CheckB4(I32+EQI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=eq(a,b)', [F, F, F, F]); +CheckB4(I32+EQI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=eq(a,b)', [F, F, F, F]); +CheckB4(I32+EQI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=eq(a,b)', [T, F, F, F]); + +CheckB4(I32+NEI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=ne(a,b)', [T, T, T, T]); +CheckB4(I32+NEI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=ne(a,b)', [T, T, T, T]); +CheckB4(I32+NEI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=ne(a,b)', [F, T, T, T]); + +CheckB4(I32+LTI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=lt(a,b)', [F, F, F, F]); +CheckB4(I32+LTI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=lt(a,b)', [T, T, T, T]); +CheckB4(I32+LTI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=lt(a,b)', [F, T, T, F]); + +CheckB4(I32+LEI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=le(a,b)', [F, F, F, F]); +CheckB4(I32+LEI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=le(a,b)', [T, T, T, T]); +CheckB4(I32+LEI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=le(a,b)', [T, T, T, F]); + +CheckB4(I32+GTI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=gt(a,b)', [T, T, T, T]); +CheckB4(I32+GTI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=gt(a,b)', [F, F, F, F]); +CheckB4(I32+GTI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=gt(a,b)', [F, F, F, T]); + +CheckB4(I32+GEI32, 'var x=b4(0,0,0,0); var a=i4(1,2,3,4); var b=i4(-1,1,0,2); x=ge(a,b)', [T, T, T, T]); +CheckB4(I32+GEI32, 'var x=b4(0,0,0,0); var a=i4(-1,1,0,2); var b=i4(1,2,3,4); x=ge(a,b)', [F, F, F, F]); +CheckB4(I32+GEI32, 'var x=b4(0,0,0,0); var a=i4(1,0,3,4); var b=i4(1,1,7,0); x=ge(a,b)', [T, F, F, T]); + +const EQU32 = 'var eq = u4.equal'; +const NEU32 = 'var ne = u4.notEqual'; +const LTU32 = 'var lt = u4.lessThan;'; +const LEU32 = 'var le = u4.lessThanOrEqual'; +const GTU32 = 'var gt = u4.greaterThan;'; +const GEU32 = 'var ge = u4.greaterThanOrEqual'; + +CheckB4(U32+EQU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=eq(a,b)', [F, F, F, F]); +CheckB4(U32+EQU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=eq(a,b)', [F, F, F, F]); +CheckB4(U32+EQU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=eq(a,b)', [T, F, F, F]); + +CheckB4(U32+NEU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=ne(a,b)', [T, T, T, T]); +CheckB4(U32+NEU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=ne(a,b)', [T, T, T, T]); +CheckB4(U32+NEU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=ne(a,b)', [F, T, T, T]); + +CheckB4(U32+LTU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=lt(a,b)', [T, F, F, F]); +CheckB4(U32+LTU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=lt(a,b)', [F, T, T, T]); +CheckB4(U32+LTU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=lt(a,b)', [F, T, T, F]); + +CheckB4(U32+LEU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=le(a,b)', [T, F, F, F]); +CheckB4(U32+LEU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=le(a,b)', [F, T, T, T]); +CheckB4(U32+LEU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=le(a,b)', [T, T, T, F]); + +CheckB4(U32+GTU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=gt(a,b)', [F, T, T, T]); +CheckB4(U32+GTU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=gt(a,b)', [T, F, F, F]); +CheckB4(U32+GTU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=gt(a,b)', [F, F, F, T]); + +CheckB4(U32+GEU32, 'var x=b4(0,0,0,0); var a=u4(1,2,3,4); var b=u4(-1,1,0,2); x=ge(a,b)', [F, T, T, T]); +CheckB4(U32+GEU32, 'var x=b4(0,0,0,0); var a=u4(-1,1,0,2); var b=u4(1,2,3,4); x=ge(a,b)', [T, F, F, F]); +CheckB4(U32+GEU32, 'var x=b4(0,0,0,0); var a=u4(1,0,3,4); var b=u4(1,1,7,0); x=ge(a,b)', [T, F, F, T]); + +const LTF32 = 'var lt=f4.lessThan;'; +const LEF32 = 'var le=f4.lessThanOrEqual;'; +const GTF32 = 'var gt=f4.greaterThan;'; +const GEF32 = 'var ge=f4.greaterThanOrEqual;'; +const EQF32 = 'var eq=f4.equal;'; +const NEF32 = 'var ne=f4.notEqual;'; + +assertAsmTypeFail('glob', USE_ASM + F32 + "var lt=f4.lessThan; function f() {var x=f4(1,2,3,4); var y=f4(5,6,7,8); x=lt(x,y);} return f"); + +CheckB4(F32+LTF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=lt(y,z)', [F, F, F, F]); +CheckB4(F32+LTF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=lt(y,z)', [T, T, T, T]); +CheckB4(F32+LTF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=lt(y,z)', [F, T, T, F]); +CheckB4(F32+LTF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=lt(y,z);', [F, F, F, F]); + +CheckB4(F32+LEF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=le(y,z)', [F, F, F, F]); +CheckB4(F32+LEF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=le(y,z)', [T, T, T, T]); +CheckB4(F32+LEF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=le(y,z)', [T, T, T, F]); +CheckB4(F32+LEF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=le(y,z);', [T, T, F, F]); + +CheckB4(F32+EQF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=eq(y,z)', [F, F, F, F]); +CheckB4(F32+EQF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=eq(y,z)', [F, F, F, F]); +CheckB4(F32+EQF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=eq(y,z)', [T, F, F, F]); +CheckB4(F32+EQF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=eq(y,z);', [T, T, F, F]); + +CheckB4(F32+NEF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=ne(y,z)', [T, T, T, T]); +CheckB4(F32+NEF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=ne(y,z)', [T, T, T, T]); +CheckB4(F32+NEF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=ne(y,z)', [F, T, T, T]); +CheckB4(F32+NEF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=ne(y,z);', [F, F, T, T]); + +CheckB4(F32+GTF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=gt(y,z)', [T, T, T, T]); +CheckB4(F32+GTF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=gt(y,z)', [F, F, F, F]); +CheckB4(F32+GTF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=gt(y,z)', [F, F, F, T]); +CheckB4(F32+GTF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=gt(y,z);', [F, F, F, F]); + +CheckB4(F32+GEF32, 'var y=f4(1,2,3,4); var z=f4(-1,1,0,2); var x=b4(0,0,0,0); x=ge(y,z)', [T, T, T, T]); +CheckB4(F32+GEF32, 'var y=f4(-1,1,0,2); var z=f4(1,2,3,4); var x=b4(0,0,0,0); x=ge(y,z)', [F, F, F, F]); +CheckB4(F32+GEF32, 'var y=f4(1,0,3,4); var z=f4(1,1,7,0); var x=b4(0,0,0,0); x=ge(y,z)', [T, F, F, T]); +CheckB4(F32+GEF32 + 'const nan = glob.NaN; const fround=glob.Math.fround', 'var y=f4(0,0,0,0); var z=f4(0,0,0,0); var x=b4(0,0,0,0); y=f4(fround(0.0),fround(-0.0),fround(0.0),fround(nan)); z=f4(fround(-0.0),fround(0.0),fround(nan),fround(0.0)); x=ge(y,z);', [T, T, F, F]); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + LTI32 + B32 + ANYB4 + 'function f(x){x=ci4(x); var y=i4(-1,0,4,5); var b=b4(0,0,0,0); b=lt(x,y); return anyt(b)|0;} return f'), this); +assertEq(f(SIMD.Int32x4(1,2,3,4)), 1); +assertEq(f(SIMD.Int32x4(1,2,4,5)), 0); +assertEq(f(SIMD.Int32x4(1,2,3,5)), 1); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + LTI32 + B32 + ALLB4 + 'function f(x){x=ci4(x); var y=i4(-1,0,4,5); var b=b4(0,0,0,0); b=lt(x,y); return allt(b)|0;} return f'), this); +assertEq(f(SIMD.Int32x4(-2,-2,3,4)), 1); +assertEq(f(SIMD.Int32x4(1,2,4,5)), 0); +assertEq(f(SIMD.Int32x4(1,2,3,5)), 0); + +// Conversions operators +const CVTIF = 'var cvt=f4.fromInt32x4;'; +const CVTFI = 'var cvt=i4.fromFloat32x4;'; +const CVTUF = 'var cvt=f4.fromUint32x4;'; +const CVTFU = 'var cvt=u4.fromFloat32x4;'; + +assertAsmTypeFail('glob', USE_ASM + I32 + "var cvt=i4.fromInt32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var cvt=i4.fromUint32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + U32 + "var cvt=u4.fromInt32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + U32 + "var cvt=u4.fromUint32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var cvt=f4.fromFloat32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CVTIF + "function f() {var x=i4(1,2,3,4); x=cvt(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CVTIF + "function f() {var x=f4(1,2,3,4); x=cvt(x);} return f"); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + F32 + CF32 + CI32 + CVTIF + 'function f(x){x=ci4(x); var y=f4(0,0,0,0); y=cvt(x); return cf4(y);} return f'), this); +assertEqX4(f(SIMD.Int32x4(1,2,3,4)), [1, 2, 3, 4]); +assertEqX4(f(SIMD.Int32x4(0,INT32_MIN,INT32_MAX,-1)), [0, Math.fround(INT32_MIN), Math.fround(INT32_MAX), -1]); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + U32 + U32I32 + F32 + CF32 + CI32 + CVTUF + + 'function f(x){x=ci4(x); var y=f4(0,0,0,0); y=cvt(u4i4(x)); return cf4(y);} return f'), this); +assertEqX4(f(SIMD.Int32x4(1,2,3,4)), [1, 2, 3, 4]); +assertEqX4(f(SIMD.Int32x4(0,INT32_MIN,INT32_MAX,-1)), [0, Math.fround(INT32_MAX+1), Math.fround(INT32_MAX), Math.fround(UINT32_MAX)]); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + F32 + CF32 + CVTFI + 'function f(x){x=cf4(x); var y=i4(0,0,0,0); y=cvt(x); return ci4(y);} return f'), this); +assertEqX4(f(SIMD.Float32x4(1,2,3,4)), [1, 2, 3, 4]); +// Test that INT32_MIN (exactly representable as an float32) and the first +// integer representable as an float32 can be converted. +assertEqX4(f(SIMD.Float32x4(INT32_MIN, INT32_MAX - 64, -0, 0)), [INT32_MIN, INT32_MAX - 64, 0, 0].map(Math.fround)); +// Test boundaries: first integer less than INT32_MIN and representable as a float32 +assertThrowsInstanceOf(() => f(SIMD.Float32x4(INT32_MIN - 129, 0, 0, 0)), RangeError); +// INT_MAX + 1 +assertThrowsInstanceOf(() => f(SIMD.Float32x4(Math.pow(2, 31), 0, 0, 0)), RangeError); +// Special values +assertThrowsInstanceOf(() => f(SIMD.Float32x4(NaN, 0, 0, 0)), RangeError); +assertThrowsInstanceOf(() => f(SIMD.Float32x4(Infinity, 0, 0, 0)), RangeError); +assertThrowsInstanceOf(() => f(SIMD.Float32x4(-Infinity, 0, 0, 0)), RangeError); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + U32 + I32U32 + F32 + CF32 + CVTFU + + 'function f(x){x=cf4(x); var y=u4(0,0,0,0); y=cvt(x); return ci4(i4u4(y));} return f'), this); +assertEqX4(f(SIMD.Float32x4(1,2,3,4)), [1, 2, 3, 4]); +// TODO: Test negative numbers > -1. They should truncate to 0. See https://github.com/tc39/ecmascript_simd/issues/315 +assertEqX4(SIMD.Uint32x4.fromInt32x4Bits(f(SIMD.Float32x4(0xffffff00, INT32_MAX+1, -0, 0))), + [0xffffff00, INT32_MAX+1, 0, 0].map(Math.fround)); +// Test boundaries: -1 or less. +assertThrowsInstanceOf(() => f(SIMD.Float32x4(-1, 0, 0, 0)), RangeError); +assertThrowsInstanceOf(() => f(SIMD.Float32x4(Math.pow(2, 32), 0, 0, 0)), RangeError); +// Special values +assertThrowsInstanceOf(() => f(SIMD.Float32x4(NaN, 0, 0, 0)), RangeError); +assertThrowsInstanceOf(() => f(SIMD.Float32x4(Infinity, 0, 0, 0)), RangeError); +assertThrowsInstanceOf(() => f(SIMD.Float32x4(-Infinity, 0, 0, 0)), RangeError); + +// Cast operators +const CVTIFB = 'var cvt=f4.fromInt32x4Bits;'; +const CVTFIB = 'var cvt=i4.fromFloat32x4Bits;'; + +var cast = (function() { + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + + function fromInt32Bits(x) { + i32[0] = x; + return f32[0]; + } + + function fromFloat32Bits(x) { + f32[0] = x; + return i32[0]; + } + + return { + fromInt32Bits, + fromFloat32Bits + } +})(); + +assertAsmTypeFail('glob', USE_ASM + I32 + "var cvt=i4.fromInt32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + F32 + "var cvt=f4.fromFloat32x4; return {}"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CVTIFB + "function f() {var x=i4(1,2,3,4); x=cvt(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CVTIFB + "function f() {var x=f4(1,2,3,4); x=cvt(x);} return f"); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + F32 + CVTIFB + CF32 + CI32 + 'function f(x){x=ci4(x); var y=f4(0,0,0,0); y=cvt(x); return cf4(y);} return f'), this); +assertEqX4(f(SIMD.Int32x4(1,2,3,4)), [1, 2, 3, 4].map(cast.fromInt32Bits)); +assertEqX4(f(SIMD.Int32x4(0,INT32_MIN,INT32_MAX,-1)), [0, INT32_MIN, INT32_MAX, -1].map(cast.fromInt32Bits)); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + F32 + F32A + CVTIFB + CF32 + CI32 + 'function f(x){x=ci4(x); var y=f4(0,0,0,0); var z=f4(1,1,1,1); y=cvt(x); y=f4a(y, z); return cf4(y)} return f'), this); +assertEqX4(f(SIMD.Int32x4(1,2,3,4)), [1, 2, 3, 4].map(cast.fromInt32Bits).map((x) => x+1)); +assertEqX4(f(SIMD.Int32x4(0,INT32_MIN,INT32_MAX,-1)), [0, INT32_MIN, INT32_MAX, -1].map(cast.fromInt32Bits).map((x) => x+1)); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + F32 + CF32 + CVTFIB + 'function f(x){x=cf4(x); var y=i4(0,0,0,0); y=cvt(x); return ci4(y);} return f'), this); +assertEqX4(f(SIMD.Float32x4(1,2,3,4)), [1, 2, 3, 4].map(cast.fromFloat32Bits)); +assertEqX4(f(SIMD.Float32x4(-0,NaN,+Infinity,-Infinity)), [-0, NaN, +Infinity, -Infinity].map(cast.fromFloat32Bits)); + +var f = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + F32 + CF32 + I32A + CVTFIB + 'function f(x){x=cf4(x); var y=i4(0,0,0,0); var z=i4(1,1,1,1); y=cvt(x); y=i4a(y,z); return ci4(y);} return f'), this); +assertEqX4(f(SIMD.Float32x4(1,2,3,4)), [1, 2, 3, 4].map(cast.fromFloat32Bits).map((x) => x+1)); +assertEqX4(f(SIMD.Float32x4(-0,NaN,+Infinity,-Infinity)), [-0, NaN, +Infinity, -Infinity].map(cast.fromFloat32Bits).map((x) => x+1)); + +// Bitwise ops +const ANDI32 = 'var andd=i4.and;'; +const ORI32 = 'var orr=i4.or;'; +const XORI32 = 'var xorr=i4.xor;'; + +CheckI4(ANDI32, 'var x=i4(42,1337,-1,13); var y=i4(2, 4, 7, 15); x=andd(x,y)', [42 & 2, 1337 & 4, -1 & 7, 13 & 15]); +CheckI4(ORI32, ' var x=i4(42,1337,-1,13); var y=i4(2, 4, 7, 15); x=orr(x,y)', [42 | 2, 1337 | 4, -1 | 7, 13 | 15]); +CheckI4(XORI32, 'var x=i4(42,1337,-1,13); var y=i4(2, 4, 7, 15); x=xorr(x,y)', [42 ^ 2, 1337 ^ 4, -1 ^ 7, 13 ^ 15]); + +const ANDU32 = 'var andd=u4.and;'; +const ORU32 = 'var orr=u4.or;'; +const XORU32 = 'var xorr=u4.xor;'; + +CheckU4(ANDU32, 'var x=u4(42,1337,-1,13); var y=u4(2, 4, 7, 15); x=andd(x,y)', [42 & 2, 1337 & 4, (-1 & 7) >>> 0, 13 & 15]); +CheckU4(ORU32, ' var x=u4(42,1337,-1,13); var y=u4(2, 4, 7, 15); x=orr(x,y)', [42 | 2, 1337 | 4, (-1 | 7) >>> 0, 13 | 15]); +CheckU4(XORU32, 'var x=u4(42,1337,-1,13); var y=u4(2, 4, 7, 15); x=xorr(x,y)', [42 ^ 2, 1337 ^ 4, (-1 ^ 7) >>> 0, 13 ^ 15]); + +const ANDB32 = 'var andd=b4.and;'; +const ORB32 = 'var orr=b4.or;'; +const XORB32 = 'var xorr=b4.xor;'; + +CheckB4(ANDB32, 'var x=b4(1,0,1,0); var y=b4(1,1,0,0); x=andd(x,y)', [true, false, false, false]); +CheckB4(ORB32, ' var x=b4(1,0,1,0); var y=b4(1,1,0,0); x=orr(x,y)', [true, true, true, false]); +CheckB4(XORB32, 'var x=b4(1,0,1,0); var y=b4(1,1,0,0); x=xorr(x,y)', [false, true, true, false]); + +// No bitwise ops on Float32x4. +const ANDF32 = 'var andd=f4.and;'; +const ORF32 = 'var orr=f4.or;'; +const XORF32 = 'var xorr=f4.xor;'; +const NOTF32 = 'var nott=f4.not;'; + +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + ANDF32 + 'function f() {var x=f4(42, 13.37,-1.42, 23.10); var y=f4(19.89, 2.4, 8.15, 16.36); x=andd(x,y);} return f'); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + ORF32 + 'function f() {var x=f4(42, 13.37,-1.42, 23.10); var y=f4(19.89, 2.4, 8.15, 16.36); x=orr(x,y);} return f'); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + XORF32 + 'function f() {var x=f4(42, 13.37,-1.42, 23.10); var y=f4(19.89, 2.4, 8.15, 16.36); x=xorr(x,y);} return f'); +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + NOTF32 + 'function f() {var x=f4(42, 13.37,-1.42, 23.10); x=nott(x);} return f'); + +// Logical ops +const LSHI = 'var lsh=i4.shiftLeftByScalar;' +const RSHI = 'var rsh=i4.shiftRightByScalar;' + +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + F32 + FROUND + LSHI + "function f() {var x=f4(1,2,3,4); return ci4(lsh(x,f32(42)));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + F32 + FROUND + LSHI + "function f() {var x=f4(1,2,3,4); return ci4(lsh(x,42));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + FROUND + LSHI + "function f() {var x=i4(1,2,3,4); return ci4(lsh(x,42.0));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + CI32 + FROUND + LSHI + "function f() {var x=i4(1,2,3,4); return ci4(lsh(x,f32(42)));} return f"); + +var input = 'i4(0, 1, ' + INT32_MIN + ', ' + INT32_MAX + ')'; +var vinput = [0, 1, INT32_MIN, INT32_MAX]; + +function Lsh(i) { return function(x) { return (x << (i & 31)) | 0 } } +function Rsh(i) { return function(x) { return (x >> (i & 31)) | 0 } } + +var asmLsh = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + LSHI + 'function f(x, y){x=x|0;y=y|0; var v=' + input + ';return ci4(lsh(v, x+y))} return f;'), this) +var asmRsh = asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + RSHI + 'function f(x, y){x=x|0;y=y|0; var v=' + input + ';return ci4(rsh(v, x+y))} return f;'), this) + +for (var i = 1; i < 64; i++) { + CheckI4(LSHI, 'var x=' + input + '; x=lsh(x, ' + i + ')', vinput.map(Lsh(i))); + CheckI4(RSHI, 'var x=' + input + '; x=rsh(x, ' + i + ')', vinput.map(Rsh(i))); + + assertEqX4(asmLsh(i, 3), vinput.map(Lsh(i + 3))); + assertEqX4(asmRsh(i, 3), vinput.map(Rsh(i + 3))); +} + +// Same thing for Uint32x4. +const LSHU = 'var lsh=u4.shiftLeftByScalar;' +const RSHU = 'var rsh=u4.shiftRightByScalar;' + +input = 'u4(0, 1, 0x80008000, ' + INT32_MAX + ')'; +vinput = [0, 1, 0x80008000, INT32_MAX]; + +function uLsh(i) { return function(x) { return (x << (i & 31)) >>> 0 } } +function uRsh(i) { return function(x) { return (x >>> (i & 31)) } } + +// Need to bitcast to Int32x4 before returning result. +asmLsh = asmLink(asmCompile('glob', USE_ASM + U32 + CU32 + LSHU + I32 + CI32 + I32U32 + + 'function f(x, y){x=x|0;y=y|0; var v=' + input + ';return ci4(i4u4(lsh(v, x+y)));} return f;'), this) +asmRsh = asmLink(asmCompile('glob', USE_ASM + U32 + CU32 + RSHU + I32 + CI32 + I32U32 + + 'function f(x, y){x=x|0;y=y|0; var v=' + input + ';return ci4(i4u4(rsh(v, x+y)));} return f;'), this) + +for (var i = 1; i < 64; i++) { + // Constant shifts. + CheckU4(LSHU, 'var x=' + input + '; x=lsh(x, ' + i + ')', vinput.map(uLsh(i))); + CheckU4(RSHU, 'var x=' + input + '; x=rsh(x, ' + i + ')', vinput.map(uRsh(i))); + + // Dynamically computed shifts. The asm function returns a Int32x4. + assertEqX4(SIMD.Uint32x4.fromInt32x4Bits(asmLsh(i, 3)), vinput.map(uLsh(i + 3))); + assertEqX4(SIMD.Uint32x4.fromInt32x4Bits(asmRsh(i, 3)), vinput.map(uRsh(i + 3))); +} + +// Select +const I32SEL = 'var i4sel = i4.select;' +const U32SEL = 'var u4sel = u4.select;' +const F32SEL = 'var f4sel = f4.select;' + +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var x=f4(1,2,3,4); return ci4(i4sel(x,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=f4(1,2,3,4); var x=i4(1,2,3,4); return ci4(i4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=f4(1,2,3,4); var x=f4(1,2,3,4); return ci4(i4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=i4(1,2,3,4); var x=f4(1,2,3,4); return ci4(i4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=i4(1,2,3,4); var x=i4(1,2,3,4); return ci4(i4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,2,3,4); var x=f4(1,2,3,4); return ci4(i4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,2,3,4); var x=f4(1,2,3,4); var y=i4(5,6,7,8); return ci4(i4sel(m,x,y));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,2,3,4); var x=i4(1,2,3,4); var y=f4(5,6,7,8); return ci4(i4sel(m,x,y));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,2,3,4); var x=f4(1,2,3,4); var y=f4(5,6,7,8); return ci4(i4sel(m,x,y));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,2,3,4); var x=i4(1,2,3,4); var y=b4(5,6,7,8); return ci4(i4sel(m,x,y));} return f"); + +assertAsmTypeFail('glob', USE_ASM + F32 + CF32 + F32SEL + "function f() {var m=f4(1,2,3,4); return cf4(f4sel(x,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + F32SEL + "function f() {var m=f4(1,2,3,4); var x=i4(1,2,3,4); return cf4(f4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + F32SEL + "function f() {var m=f4(1,2,3,4); var x=f4(1,2,3,4); return cf4(f4sel(m,x,x));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + F32SEL + "function f() {var m=i4(1,2,3,4); var x=f4(1,2,3,4); var y=i4(5,6,7,8); return cf4(f4sel(m,x,y));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + CF32 + F32SEL + "function f() {var m=i4(1,2,3,4); var x=i4(1,2,3,4); var y=f4(5,6,7,8); return cf4(f4sel(m,x,y));} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + CI32 + I32SEL + "function f() {var m=b4(0,0,0,0); var x=i4(1,2,3,4); var y=i4(5,6,7,8); return ci4(i4sel(m,x,y)); } return f"), this)(), [5, 6, 7, 8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + CI32 + I32SEL + "function f() {var m=b4(1,1,1,1); var x=i4(1,2,3,4); var y=i4(5,6,7,8); return ci4(i4sel(m,x,y)); } return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + CI32 + I32SEL + "function f() {var m=b4(0,1,0,1); var x=i4(1,2,3,4); var y=i4(5,6,7,8); return ci4(i4sel(m,x,y)); } return f"), this)(), [5, 2, 7, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + CI32 + I32SEL + "function f() {var m=b4(0,0,1,1); var x=i4(1,2,3,4); var y=i4(5,6,7,8); return ci4(i4sel(m,x,y)); } return f"), this)(), [5, 6, 3, 4]); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + F32 + CF32 + F32SEL + "function f() {var m=b4(0,0,0,0); var x=f4(1,2,3,4); var y=f4(5,6,7,8); return cf4(f4sel(m,x,y)); } return f"), this)(), [5, 6, 7, 8]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + F32 + CF32 + F32SEL + "function f() {var m=b4(1,1,1,1); var x=f4(1,2,3,4); var y=f4(5,6,7,8); return cf4(f4sel(m,x,y)); } return f"), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + F32 + CF32 + F32SEL + "function f() {var m=b4(0,1,0,1); var x=f4(1,2,3,4); var y=f4(5,6,7,8); return cf4(f4sel(m,x,y)); } return f"), this)(), [5, 2, 7, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + B32 + F32 + CF32 + F32SEL + "function f() {var m=b4(0,0,1,1); var x=f4(1,2,3,4); var y=f4(5,6,7,8); return cf4(f4sel(m,x,y)); } return f"), this)(), [5, 6, 3, 4]); + +CheckU4(B32 + U32SEL, "var m=b4(0,0,0,0); var x=u4(1,2,3,4); var y=u4(5,6,7,8); x=u4sel(m,x,y);", [5, 6, 7, 8]); +CheckU4(B32 + U32SEL, "var m=b4(1,1,1,1); var x=u4(1,2,3,4); var y=u4(5,6,7,8); x=u4sel(m,x,y);", [1, 2, 3, 4]); +CheckU4(B32 + U32SEL, "var m=b4(0,1,0,1); var x=u4(1,2,3,4); var y=u4(5,6,7,8); x=u4sel(m,x,y);", [5, 2, 7, 4]); +CheckU4(B32 + U32SEL, "var m=b4(0,0,1,1); var x=u4(1,2,3,4); var y=u4(5,6,7,8); x=u4sel(m,x,y);", [5, 6, 3, 4]); + +// Splat +const I32SPLAT = 'var splat=i4.splat;' +const U32SPLAT = 'var splat=u4.splat;' +const F32SPLAT = 'var splat=f4.splat;' +const B32SPLAT = 'var splat=b4.splat;' + +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + I32SPLAT + "function f() {var m=i4(1,2,3,4); var p=f4(1.,2.,3.,4.); p=splat(f32(1));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32SPLAT + "function f() {var m=i4(1,2,3,4); m=splat(1, 2)} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32SPLAT + "function f() {var m=i4(1,2,3,4); m=splat()} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + I32SPLAT + "function f() {var m=i4(1,2,3,4); m=splat(m);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32SPLAT + "function f() {var m=i4(1,2,3,4); m=splat(1.0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + I32SPLAT + FROUND + "function f() {var m=i4(1,2,3,4); m=splat(f32(1.0));} return f"); + +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + I32SPLAT + 'function f(){return ci4(splat(42));} return f'), this)(), [42, 42, 42, 42]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + B32SPLAT + 'function f(){return cb4(splat(42));} return f'), this)(), [true, true, true, true]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + B32 + CB32 + B32SPLAT + 'function f(){return cb4(splat(0));} return f'), this)(), [false, false, false, false]); +CheckU4(B32 + U32SPLAT, "var x=u4(1,2,3,4); x=splat(0);", [0, 0, 0, 0]); +CheckU4(B32 + U32SPLAT, "var x=u4(1,2,3,4); x=splat(0xaabbccdd);", [0xaabbccdd, 0xaabbccdd, 0xaabbccdd, 0xaabbccdd]); + +const l33t = Math.fround(13.37); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(f32(1)));} return f'), this)(), [1, 1, 1, 1]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(1.0));} return f'), this)(), [1, 1, 1, 1]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(f32(1 >>> 0)));} return f'), this)(), [1, 1, 1, 1]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(f32(13.37)));} return f'), this)(), [l33t, l33t, l33t, l33t]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(13.37));} return f'), this)(), [l33t, l33t, l33t, l33t]); + +var i32view = new Int32Array(heap); +var f32view = new Float32Array(heap); +i32view[0] = 42; +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + I32 + CI32 + I32SPLAT + 'var i32=new glob.Int32Array(heap); function f(){return ci4(splat(i32[0]));} return f'), this, {}, heap)(), [42, 42, 42, 42]); +f32view[0] = 42; +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + F32 + CF32 + F32SPLAT + 'var f32=new glob.Float32Array(heap); function f(){return cf4(splat(f32[0]));} return f'), this, {}, heap)(), [42, 42, 42, 42]); +assertEqX4(asmLink(asmCompile('glob', 'ffi', 'heap', USE_ASM + F32 + CF32 + F32SPLAT + FROUND + 'function f(){return cf4(splat(f32(1) + f32(2)));} return f'), this, {}, heap)(), [3, 3, 3, 3]); + +// Dead code +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + 'function f(){var x=i4(1,2,3,4); return ci4(x); x=i4(5,6,7,8); return ci4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + EXTI4 + 'function f(){var x=i4(1,2,3,4); var c=0; return ci4(x); c=e(x,0)|0; return ci4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + I32A + 'function f(){var x=i4(1,2,3,4); var c=0; return ci4(x); x=i4a(x,x); return ci4(x);} return f'), this)(), [1, 2, 3, 4]); +assertEqX4(asmLink(asmCompile('glob', USE_ASM + I32 + CI32 + I32S + 'function f(){var x=i4(1,2,3,4); var c=0; return ci4(x); x=i4s(x,x); return ci4(x);} return f'), this)(), [1, 2, 3, 4]); + +// Swizzle +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); x=swizzle(x, -1, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); x=swizzle(x, 4, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); x=swizzle(x, 0.0, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); x=swizzle(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); x=swizzle(x, 0, 0, 0, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var swizzle=i4.swizzle; function f() {var x=i4(1,2,3,4); var y=42; x=swizzle(x, 0, 0, 0, y);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "var swizzle=i4.swizzle; function f() {var x=f4(1,2,3,4); x=swizzle(x, 0, 0, 0, 0);} return f"); + +function swizzle(arr, lanes) { + return [arr[lanes[0]], arr[lanes[1]], arr[lanes[2]], arr[lanes[3]]]; +} + +var before = Date.now(); +for (var i = 0; i < Math.pow(4, 4); i++) { + var lanes = [i & 3, (i >> 2) & 3, (i >> 4) & 3, (i >> 6) & 3]; + CheckI4('var swizzle=i4.swizzle;', 'var x=i4(1,2,3,4); x=swizzle(x, ' + lanes.join(',') + ')', swizzle([1,2,3,4], lanes)); + CheckU4('var swizzle=u4.swizzle;', 'var x=u4(1,2,3,4); x=swizzle(x, ' + lanes.join(',') + ')', swizzle([1,2,3,4], lanes)); + CheckF4('var swizzle=f4.swizzle;', 'var x=f4(1,2,3,4); x=swizzle(x, ' + lanes.join(',') + ')', swizzle([1,2,3,4], lanes)); +} +DEBUG && print('time for checking all swizzles:', Date.now() - before); + +// Shuffle +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x, y, -1, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x, y, 8, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x, y, 0.0, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x, 0, 0, 0, 0, 0);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); x=shuffle(x, y, 0, 0, 0, x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var shuffle=i4.shuffle; function f() {var x=i4(1,2,3,4); var y=i4(1,2,3,4); var z=42; x=shuffle(x, y, 0, 0, 0, z);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + F32 + "var shuffle=i4.shuffle; function f() {var x=f4(1,2,3,4); x=shuffle(x, x, 0, 0, 0, 0);} return f"); + +function shuffle(lhs, rhs, lanes) { + return [(lanes[0] < 4 ? lhs : rhs)[lanes[0] % 4], + (lanes[1] < 4 ? lhs : rhs)[lanes[1] % 4], + (lanes[2] < 4 ? lhs : rhs)[lanes[2] % 4], + (lanes[3] < 4 ? lhs : rhs)[lanes[3] % 4]]; +} + +before = Date.now(); + +const LANE_SELECTORS = [ + // Four of lhs or four of rhs, equivalent to swizzle + [0, 1, 2, 3], + [4, 5, 6, 7], + [0, 2, 3, 1], + [4, 7, 4, 6], + // One of lhs, three of rhs + [0, 4, 5, 6], + [4, 0, 5, 6], + [4, 5, 0, 6], + [4, 5, 6, 0], + // Two of lhs, two of rhs + // in one shufps + [1, 2, 4, 5], + [4, 5, 1, 2], + // in two shufps + [7, 0, 5, 2], + [0, 7, 5, 2], + [0, 7, 2, 5], + [7, 0, 2, 5], + // Three of lhs, one of rhs + [7, 0, 1, 2], + [0, 7, 1, 2], + [0, 1, 7, 2], + [0, 1, 2, 7], + // Impl-specific special cases for swizzle + [2, 3, 2, 3], + [0, 1, 0, 1], + [0, 0, 1, 1], + [2, 2, 3, 3], + // Impl-specific special cases for shuffle (case and swapped case) + [2, 3, 6, 7], [6, 7, 2, 3], + [0, 1, 4, 5], [4, 5, 0, 1], + [0, 4, 1, 5], [4, 0, 5, 1], + [2, 6, 3, 7], [6, 2, 7, 3], + [4, 1, 2, 3], [0, 5, 6, 7], + // Insert one element from rhs into lhs keeping other elements unchanged + [7, 1, 2, 3], + [0, 7, 2, 3], + [0, 1, 7, 2], + // These are effectively vector selects + [0, 5, 2, 3], + [0, 1, 6, 3], + [4, 5, 2, 3], + [4, 1, 6, 3] +]; + +for (var lanes of LANE_SELECTORS) { + CheckI4('var shuffle=i4.shuffle;', 'var x=i4(1,2,3,4); var y=i4(5,6,7,8); x=shuffle(x, y, ' + lanes.join(',') + ')', shuffle([1,2,3,4], [5,6,7,8], lanes)); + CheckU4('var shuffle=u4.shuffle;', 'var x=u4(1,2,3,4); var y=u4(5,6,7,8); x=shuffle(x, y, ' + lanes.join(',') + ')', shuffle([1,2,3,4], [5,6,7,8], lanes)); + CheckF4('var shuffle=f4.shuffle;', 'var x=f4(1,2,3,4); var y=f4(5,6,7,8); x=shuffle(x, y, ' + lanes.join(',') + ')', shuffle([1,2,3,4], [5,6,7,8], lanes)); +} +DEBUG && print('time for checking all shuffles:', Date.now() - before); + +// 3. Function calls +// 3.1. No math builtins +assertAsmTypeFail('glob', USE_ASM + I32 + "var fround=glob.Math.fround; function f() {var x=i4(1,2,3,4); return +fround(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var sin=glob.Math.sin; function f() {var x=i4(1,2,3,4); return +sin(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var ceil=glob.Math.ceil; function f() {var x=i4(1,2,3,4); return +ceil(x);} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var pow=glob.Math.pow; function f() {var x=i4(1,2,3,4); return +pow(1.0, x);} return f"); + +assertAsmTypeFail('glob', USE_ASM + I32 + "var fround=glob.Math.fround; function f() {var x=i4(1,2,3,4); x=i4(fround(3));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var sin=glob.Math.sin; function f() {var x=i4(1,2,3,4); x=i4(sin(3.0));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var ceil=glob.Math.sin; function f() {var x=i4(1,2,3,4); x=i4(ceil(3.0));} return f"); +assertAsmTypeFail('glob', USE_ASM + I32 + "var pow=glob.Math.pow; function f() {var x=i4(1,2,3,4); x=i4(pow(1.0, 2.0));} return f"); + +// 3.2. FFI calls +// Can't pass SIMD arguments to FFI +assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); func(x);} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + U32 + "var func=ffi.func; function f() {var x=u4(1,2,3,4); func(x);} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); func(x);} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + B32 + "var func=ffi.func; function f() {var x=b4(1,2,3,4); func(x);} return f"); + +// Can't have FFI return SIMD values +assertAsmTypeFail('glob', 'ffi', USE_ASM + I32 + "var func=ffi.func; function f() {var x=i4(1,2,3,4); x=i4(func());} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + U32 + "var func=ffi.func; function f() {var x=u4(1,2,3,4); x=i4(func());} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + F32 + "var func=ffi.func; function f() {var x=f4(1,2,3,4); x=f4(func());} return f"); +assertAsmTypeFail('glob', 'ffi', USE_ASM + B32 + "var func=ffi.func; function f() {var x=b4(1,2,3,4); x=b4(func());} return f"); + +// 3.3 Internal calls +// asm.js -> asm.js +// Retrieving values from asm.js +var code = USE_ASM + I32 + CI32 + I32A + EXTI4 + ` + var check = ffi.check; + + function g() { + var i = 0; + var y = i4(0,0,0,0); + var tmp = i4(0,0,0,0); var z = i4(1,1,1,1); + var w = i4(5,5,5,5); + for (; (i|0) < 30; i = i + 1 |0) + y = i4a(z, y); + y = i4a(w, y); + check(e(y,0) | 0, e(y,1) | 0, e(y,2) | 0, e(y,3) | 0); + return ci4(y); + } + + function f(x) { + x = ci4(x); + var y = i4(0,0,0,0); + y = ci4(g()); + check(e(y,0) | 0, e(y,1) | 0, e(y,2) | 0, e(y,3) | 0); + return ci4(x); + } + return f; +`; + +var v4 = SIMD.Int32x4(1,2,3,4); +function check(x, y, z, w) { + assertEq(x, 35); + assertEq(y, 35); + assertEq(z, 35); + assertEq(w, 35); +} +var ffi = {check}; +assertEqX4(asmLink(asmCompile('glob', 'ffi', code), this, ffi)(v4), [1,2,3,4]); + +// Passing arguments from asm.js to asm.js +var code = USE_ASM + I32 + CI32 + I32A + EXTI4 + ` + var assertEq = ffi.assertEq; + + function internal([args]) { + [coerc] + assertEq(e([last],0) | 0, [i] | 0); + assertEq(e([last],1) | 0, [i] + 1 |0); + assertEq(e([last],2) | 0, [i] + 2 |0); + assertEq(e([last],3) | 0, [i] + 3 |0); + } + + function external() { + [decls] + internal([args]); + } + return external; +`; + +var ffi = {assertEq}; +var args = ''; +var decls = ''; +var coerc = ''; +for (var i = 1; i < 10; ++i) { + var j = i; + args += ((i > 1) ? ', ':'') + 'x' + i; + decls += 'var x' + i + ' = i4(' + j++ + ', ' + j++ + ', ' + j++ + ', ' + j++ + ');\n'; + coerc += 'x' + i + ' = ci4(x' + i + ');\n'; + last = 'x' + i; + var c = code.replace(/\[args\]/g, args) + .replace(/\[last\]/g, last) + .replace(/\[decls\]/i, decls) + .replace(/\[coerc\]/i, coerc) + .replace(/\[i\]/g, i); + asmLink(asmCompile('glob', 'ffi', c), this, ffi)(); +} + +// Bug 1240524 +assertAsmTypeFail(USE_ASM + B32 + 'var x = b4(0, 0, 0, 0); frd(x);'); + +// Passing boolean results to extern functions. +// Verify that these functions are typed correctly. +function isone(x) { return (x===1)|0 } +var f = asmLink(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + ANYB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(anyt(i)|0)|0; } return f'), this, {isone:isone}); +assertEq(f(SIMD.Bool32x4(0,0,1,0)), 1) +assertEq(f(SIMD.Bool32x4(0,0,0,0)), 0) +assertAsmTypeFail('glob', 'ffi', USE_ASM + B32 + CB32 + ANYB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(anyt(i))|0; } return f'); + +var f = asmLink(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + ALLB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(allt(i)|0)|0; } return f'), this, {isone:isone}); +assertEq(f(SIMD.Bool32x4(1,1,1,1)), 1) +assertEq(f(SIMD.Bool32x4(0,1,0,0)), 0) +assertAsmTypeFail('glob', 'ffi', USE_ASM + B32 + CB32 + ALLB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(allt(i))|0; } return f'); + +var f = asmLink(asmCompile('glob', 'ffi', USE_ASM + B32 + CB32 + EXTB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(e(i,2)|0)|0; } return f'), this, {isone:isone}); +assertEq(f(SIMD.Bool32x4(1,1,1,1)), 1) +assertEq(f(SIMD.Bool32x4(0,1,0,0)), 0) +assertAsmTypeFail('glob', 'ffi', USE_ASM + B32 + CB32 + EXTB4 + 'var isone=ffi.isone; function f(i) { i=cb4(i); return isone(e(i,2))|0; } return f'); + +// Stress-test for register spilling code and stack depth checks +var code = ` + "use asm"; + var i4 = glob.SIMD.Int32x4; + var i4a = i4.add; + var e = i4.extractLane; + var assertEq = ffi.assertEq; + function g() { + var x = i4(1,2,3,4); + var y = i4(2,3,4,5); + var z = i4(0,0,0,0); + z = i4a(x, y); + assertEq(e(z,0) | 0, 3); + assertEq(e(z,1) | 0, 5); + assertEq(e(z,2) | 0, 7); + assertEq(e(z,3) | 0, 9); + } + return g +` +asmLink(asmCompile('glob', 'ffi', code), this, assertEqFFI)(); + +(function() { + var code = ` + "use asm"; + var i4 = glob.SIMD.Int32x4; + var i4a = i4.add; + var e = i4.extractLane; + var assertEq = ffi.assertEq; + var one = ffi.one; + + // Function call with arguments on the stack (1 on x64, 3 on x86) + function h(x1, x2, x3, x4, x5, x6, x7) { + x1=x1|0 + x2=x2|0 + x3=x3|0 + x4=x4|0 + x5=x5|0 + x6=x6|0 + x7=x7|0 + return x1 + x2 |0 + } + + function g() { + var x = i4(1,2,3,4); + var y = i4(2,3,4,5); + var z = i4(0,0,0,0); + var w = 1; + z = i4a(x, y); + w = w + (one() | 0) | 0; + assertEq(e(z,0) | 0, 3); + assertEq(e(z,1) | 0, 5); + assertEq(e(z,2) | 0, 7); + assertEq(e(z,3) | 0, 9); + h(1, 2, 3, 4, 42, 42, 42)|0 + return w | 0; + } + return g + `; + + asmLink(asmCompile('glob', 'ffi', code), this, {assertEq: assertEq, one: () => 1})(); +})(); + +// Function calls with mixed arguments on the stack (SIMD and scalar). In the +// worst case (x64), we have 6 int arg registers and 8 float registers. +(function() { + var code = ` + "use asm"; + var i4 = glob.SIMD.Int32x4; + var e = i4.extractLane; + var ci4 = i4.check; + function h( + // In registers: + gpr1, gpr2, gpr3, gpr4, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, + // On the stack: + sint1, ssimd1, sdouble1, ssimd2, sint2, sint3, sint4, ssimd3, sdouble2 + ) + { + gpr1=gpr1|0; + gpr2=gpr2|0; + gpr3=gpr3|0; + gpr4=gpr4|0; + + xmm1=+xmm1; + xmm2=+xmm2; + xmm3=+xmm3; + xmm4=+xmm4; + xmm5=+xmm5; + xmm6=+xmm6; + xmm7=+xmm7; + xmm8=+xmm8; + + sint1=sint1|0; + ssimd1=ci4(ssimd1); + sdouble1=+sdouble1; + ssimd2=ci4(ssimd2); + sint2=sint2|0; + sint3=sint3|0; + sint4=sint4|0; + ssimd3=ci4(ssimd3); + sdouble2=+sdouble2; + + return (e(ssimd1,0)|0) + (e(ssimd2,1)|0) + (e(ssimd3,2)|0) + sint2 + gpr3 | 0; + } + + function g() { + var simd1 = i4(1,2,3,4); + var simd2 = i4(5,6,7,8); + var simd3 = i4(9,10,11,12); + return h(1, 2, 3, 4, + 1., 2., 3., 4., 5., 6., 7., 8., + 5, simd1, 9., simd2, 6, 7, 8, simd3, 10.) | 0; + } + return g + `; + + assertEq(asmLink(asmCompile('glob', 'ffi', code), this)(), 1 + 6 + 11 + 6 + 3); +})(); + +// Check that the interrupt callback doesn't erase high components of simd +// registers: + +// WARNING: must be the last test in this file +(function() { + var iters = 2000000; + var code = ` + "use asm"; + var i4 = glob.SIMD.Int32x4; + var i4a = i4.add; + var ci4 = i4.check; + function _() { + var i = 0; + var n = i4(0,0,0,0); + var one = i4(1,1,1,1); + for (; (i>>>0) < ` + iters + `; i=(i+1)>>>0) { + n = i4a(n, one); + } + return ci4(n); + } + return _;`; + // This test relies on the fact that setting the timeout will call the + // interrupt callback at fixed intervals, even before the timeout. + timeout(1000); + var x4 = asmLink(asmCompile('glob', code), this)(); + assertEq(SIMD.Int32x4.extractLane(x4,0), iters); + assertEq(SIMD.Int32x4.extractLane(x4,1), iters); + assertEq(SIMD.Int32x4.extractLane(x4,2), iters); + assertEq(SIMD.Int32x4.extractLane(x4,3), iters); +})(); + +} catch(e) { + print('Stack:', e.stack) + print('Error:', e) + throw e; +} diff --git a/js/src/jit-test/tests/asm.js/testZOOB.js b/js/src/jit-test/tests/asm.js/testZOOB.js index 77b8b8f5f324..3ab740fb427a 100644 --- a/js/src/jit-test/tests/asm.js/testZOOB.js +++ b/js/src/jit-test/tests/asm.js/testZOOB.js @@ -100,6 +100,106 @@ function assertEqX4(observed, expected) { assertEq(observed.w, expected.w); } +function testSimdX4(ctor, shift, scale, disp, simdName, simdCtor) { + var arr = new ctor(ab); + + var c = asmCompile('glob', 'imp', 'b', + USE_ASM + + 'var arr=new glob.' + ctor.name + '(b); ' + + 'var SIMD_' + simdName + ' = glob.SIMD.' + simdName + '; ' + + 'var SIMD_' + simdName + '_check = SIMD_' + simdName + '.check; ' + + 'var SIMD_' + simdName + '_load = SIMD_' + simdName + '.load; ' + + 'var SIMD_' + simdName + '_load2 = SIMD_' + simdName + '.load2; ' + + 'var SIMD_' + simdName + '_load1 = SIMD_' + simdName + '.load1; ' + + 'var SIMD_' + simdName + '_store = SIMD_' + simdName + '.store; ' + + 'var SIMD_' + simdName + '_store2 = SIMD_' + simdName + '.store2; ' + + 'var SIMD_' + simdName + '_store1 = SIMD_' + simdName + '.store1; ' + + 'function load(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function load2(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function load1(i) {i=i|0; return SIMD_' + simdName + '_check(SIMD_' + simdName + '_load1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ')) } ' + + 'function store(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'function store2(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store2(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'function store1(i,j) {i=i|0;j=SIMD_' + simdName + '_check(j); SIMD_' + simdName + '_store1(arr, ((i<<' + scale + ')+' + disp + ')>>' + shift + ', j) } ' + + 'return { load: load, load2: load2, load1: load1, store: store, store2 : store2, store1 : store1 }'); + var f = asmLink(c, this, null, ab); + + const RuntimeError = WebAssembly.RuntimeError; + + for (var i of indices) { + var index = ((i<>shift; + + var v, v2, v1; + var t = false, t2 = false, t1 = false; + try { v = simdCtor.load(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t = true; + } + try { v2 = simdCtor.load2(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t2 = true; + } + try { v1 = simdCtor.load1(arr, index); } + catch (e) { + assertEq(e instanceof RangeError, true); + t1 = true; + } + + // Loads + var l, l2, l1; + var r = false, r2 = false, r1 = false; + try { l = f.load(i); } + catch (e) { + assertEq(e instanceof RuntimeError, true); + r = true; + } + try { l2 = f.load2(i); } + catch (e) { + assertEq(e instanceof RuntimeError, true); + r2 = true; + } + try { l1 = f.load1(i); } + catch (e) { + assertEq(e instanceof RuntimeError, true); + r1 = true; + } + assertEq(t, r); + assertEq(t2, r2); + assertEq(t1, r1); + if (!t) assertEqX4(v, l); + if (!t2) assertEqX4(v2, l2); + if (!t1) assertEqX4(v1, l1); + + // Stores + if (!t) { + simdCtor.store(arr, index, simdCtor.neg(v)); + f.store(i, v); + assertEqX4(simdCtor.load(arr, index), v); + } else + assertThrowsInstanceOf(() => f.store(i, simdCtor()), RuntimeError); + if (!t2) { + simdCtor.store2(arr, index, simdCtor.neg(v2)); + f.store2(i, v2); + assertEqX4(simdCtor.load2(arr, index), v2); + } else + assertThrowsInstanceOf(() => f.store2(i, simdCtor()), RuntimeError); + if (!t1) { + simdCtor.store1(arr, index, simdCtor.neg(v1)); + f.store1(i, v1); + assertEqX4(simdCtor.load1(arr, index), v1); + } else + assertThrowsInstanceOf(() => f.store1(i, simdCtor()), RuntimeError); + } +} + +function testFloat32x4(ctor, shift, scale, disp) { + testSimdX4(ctor, shift, scale, disp, 'Float32x4', SIMD.Float32x4); +} +function testInt32x4(ctor, shift, scale, disp) { + testSimdX4(ctor, shift, scale, disp, 'Int32x4', SIMD.Int32x4); +} + function test(tester, ctor, shift) { var arr = new ctor(ab); for (var i = 0; i < arr.length; i++) @@ -123,3 +223,15 @@ test(testInt, Int32Array, 2); test(testInt, Uint32Array, 2); test(testFloat32, Float32Array, 2); test(testFloat64, Float64Array, 3); +if (typeof SIMD !== 'undefined' && isSimdAvailable()) { + // Avoid pathological --ion-eager compile times due to bails in loops + setJitCompilerOption('ion.warmup.trigger', 1000000); + + // Use a fresh ArrayBuffer so prepareForAsmJS can allocated a guard page + // which SIMD.js needs. Since the original ArrayBuffer was prepared for + // asm.js that didn't use SIMD.js, it has no guard page (on 32-bit). + ab = new ArrayBuffer(BUF_MIN); + + test(testInt32x4, Uint8Array, 0); + test(testFloat32x4, Uint8Array, 0); +} diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index a062b63e642a..b91ee89c0d5b 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -2012,6 +2012,7 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) case Bailout_NonObjectInput: case Bailout_NonStringInput: case Bailout_NonSymbolInput: + case Bailout_UnexpectedSimdInput: case Bailout_NonSharedTypedArrayInput: case Bailout_Debugger: case Bailout_UninitializedThis: diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index a15d31272986..0ee438e4831a 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -14,6 +14,7 @@ #include "jstypes.h" #include "builtin/Eval.h" +#include "builtin/SIMDConstants.h" #include "gc/Policy.h" #include "jit/BaselineCacheIRCompiler.h" #include "jit/BaselineDebugModeOSR.h" @@ -2012,6 +2013,71 @@ TryAttachFunCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, return true; } +// Check if target is a native SIMD operation which returns a SIMD type. +// If so, set res to a template object matching the SIMD type produced and return true. +static bool +GetTemplateObjectForSimd(JSContext* cx, JSFunction* target, MutableHandleObject res) +{ + if (!target->hasJitInfo()) + return false; + + const JSJitInfo* jitInfo = target->jitInfo(); + if (jitInfo->type() != JSJitInfo::InlinableNative) + return false; + + // Check if this is a native inlinable SIMD operation. + SimdType ctrlType; + switch (jitInfo->inlinableNative) { + case InlinableNative::SimdInt8x16: ctrlType = SimdType::Int8x16; break; + case InlinableNative::SimdUint8x16: ctrlType = SimdType::Uint8x16; break; + case InlinableNative::SimdInt16x8: ctrlType = SimdType::Int16x8; break; + case InlinableNative::SimdUint16x8: ctrlType = SimdType::Uint16x8; break; + case InlinableNative::SimdInt32x4: ctrlType = SimdType::Int32x4; break; + case InlinableNative::SimdUint32x4: ctrlType = SimdType::Uint32x4; break; + case InlinableNative::SimdFloat32x4: ctrlType = SimdType::Float32x4; break; + case InlinableNative::SimdBool8x16: ctrlType = SimdType::Bool8x16; break; + case InlinableNative::SimdBool16x8: ctrlType = SimdType::Bool16x8; break; + case InlinableNative::SimdBool32x4: ctrlType = SimdType::Bool32x4; break; + // This is not an inlinable SIMD operation. + default: return false; + } + + // The controlling type is not necessarily the return type. + // Check the actual operation. + SimdOperation simdOp = SimdOperation(jitInfo->nativeOp); + SimdType retType; + + switch(simdOp) { + case SimdOperation::Fn_allTrue: + case SimdOperation::Fn_anyTrue: + case SimdOperation::Fn_extractLane: + // These operations return a scalar. No template object needed. + return false; + + case SimdOperation::Fn_lessThan: + case SimdOperation::Fn_lessThanOrEqual: + case SimdOperation::Fn_equal: + case SimdOperation::Fn_notEqual: + case SimdOperation::Fn_greaterThan: + case SimdOperation::Fn_greaterThanOrEqual: + // These operations return a boolean vector with the same shape as the + // controlling type. + retType = GetBooleanSimdType(ctrlType); + break; + + default: + // All other operations return the controlling type. + retType = ctrlType; + break; + } + + // Create a template object based on retType. + RootedGlobalObject global(cx, cx->global()); + Rooted descr(cx, GlobalObject::getOrCreateSimdTypeDescr(cx, global, retType)); + res.set(cx->realm()->jitRealm()->getSimdTemplateObjectFor(cx, descr)); + return true; +} + static bool GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& args, MutableHandleObject res, bool* skipAttach) @@ -2095,6 +2161,9 @@ GetTemplateObjectForNative(JSContext* cx, HandleFunction target, const CallArgs& return !!res; } + if (JitSupportsSimd() && GetTemplateObjectForSimd(cx, target, res)) + return !!res; + return true; } @@ -2111,6 +2180,12 @@ GetTemplateObjectForClassHook(JSContext* cx, JSNative hook, CallArgs& args, return !!templateObject; } + if (hook == SimdTypeDescr::call && JitSupportsSimd()) { + Rooted descr(cx, &args.callee().as()); + templateObject.set(cx->realm()->jitRealm()->getSimdTemplateObjectFor(cx, descr)); + return !!templateObject; + } + return true; } diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index 68a4351818cf..b5de2f477cf8 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -663,6 +663,25 @@ BaselineInspector::getTemplateObjectForClassHook(jsbytecode* pc, const Class* cl return nullptr; } +JSObject* +BaselineInspector::getTemplateObjectForSimdCtor(jsbytecode* pc, SimdType simdType) +{ + if (!hasBaselineScript()) + return nullptr; + + const ICEntry& entry = icEntryFromPC(pc); + for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) { + if (stub->isCall_ClassHook() && stub->toCall_ClassHook()->clasp() == &SimdTypeDescr::class_) { + JSObject* templateObj = stub->toCall_ClassHook()->templateObject(); + InlineTypedObject& typedObj = templateObj->as(); + if (typedObj.typeDescr().as().type() == simdType) + return templateObj; + } + } + + return nullptr; +} + LexicalEnvironmentObject* BaselineInspector::templateNamedLambdaObject() { diff --git a/js/src/jit/BaselineInspector.h b/js/src/jit/BaselineInspector.h index 4c3b4cc2930f..cbf8532df5ce 100644 --- a/js/src/jit/BaselineInspector.h +++ b/js/src/jit/BaselineInspector.h @@ -131,6 +131,7 @@ class BaselineInspector JSObject* getTemplateObject(jsbytecode* pc); JSObject* getTemplateObjectForNative(jsbytecode* pc, Native native); JSObject* getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp); + JSObject* getTemplateObjectForSimdCtor(jsbytecode* pc, SimdType simdType); // Sometimes the group a template object will have is known, even if the // object itself isn't. diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index fa47b88bccc5..6324178f2ca9 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -422,6 +422,7 @@ CodeGenerator::CodeGenerator(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* : CodeGeneratorSpecific(gen, graph, masm) , ionScriptLabels_(gen->alloc()) , scriptCounts_(nullptr) + , simdTemplatesToReadBarrier_(0) , realmStubsToReadBarrier_(0) { } @@ -6426,6 +6427,82 @@ CodeGenerator::visitNewTypedObject(LNewTypedObject* lir) masm.bind(ool->rejoin()); } +void +CodeGenerator::visitSimdBox(LSimdBox* lir) +{ + FloatRegister in = ToFloatRegister(lir->input()); + Register object = ToRegister(lir->output()); + Register temp = ToRegister(lir->temp()); + InlineTypedObject* templateObject = lir->mir()->templateObject(); + gc::InitialHeap initialHeap = lir->mir()->initialHeap(); + MIRType type = lir->mir()->input()->type(); + + addSimdTemplateToReadBarrier(lir->mir()->simdType()); + + MOZ_ASSERT(lir->safepoint()->liveRegs().has(in), "Save the input register across oolCallVM"); + OutOfLineCode* ool = oolCallVM(NewTypedObjectInfo, lir, + ArgList(ImmGCPtr(templateObject), Imm32(initialHeap)), + StoreRegisterTo(object)); + + TemplateObject templateObj(templateObject); + masm.createGCObject(object, temp, templateObj, initialHeap, ool->entry()); + masm.bind(ool->rejoin()); + + Address objectData(object, InlineTypedObject::offsetOfDataStart()); + switch (type) { + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + masm.storeUnalignedSimd128Int(in, objectData); + break; + case MIRType::Float32x4: + masm.storeUnalignedSimd128Float(in, objectData); + break; + default: + MOZ_CRASH("Unknown SIMD kind when generating code for SimdBox."); + } +} + +void +CodeGenerator::addSimdTemplateToReadBarrier(SimdType simdType) +{ + simdTemplatesToReadBarrier_ |= 1 << uint32_t(simdType); +} + +void +CodeGenerator::visitSimdUnbox(LSimdUnbox* lir) +{ + Register object = ToRegister(lir->input()); + FloatRegister simd = ToFloatRegister(lir->output()); + Register temp = ToRegister(lir->temp()); + Label bail; + + masm.branchIfNotSimdObject(object, temp, lir->mir()->simdType(), &bail); + + // Load the value from the data of the InlineTypedObject. + Address objectData(object, InlineTypedObject::offsetOfDataStart()); + switch (lir->mir()->type()) { + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + masm.loadUnalignedSimd128Int(objectData, simd); + break; + case MIRType::Float32x4: + masm.loadUnalignedSimd128Float(objectData, simd); + break; + default: + MOZ_CRASH("The impossible happened!"); + } + + bailoutFrom(&bail, lir->snapshot()); +} + typedef js::NamedLambdaObject* (*NewNamedLambdaObjectFn)(JSContext*, HandleFunction, gc::InitialHeap); static const VMFunction NewNamedLambdaObjectInfo = FunctionInfo(NamedLambdaObject::createTemplateObject, @@ -7156,7 +7233,7 @@ CodeGenerator::visitWasmLoadGlobalVar(LWasmLoadGlobalVar* ins) MWasmLoadGlobalVar* mir = ins->mir(); MIRType type = mir->type(); - MOZ_ASSERT(IsNumberType(type)); + MOZ_ASSERT(IsNumberType(type) || IsSimdType(type)); Register tls = ToRegister(ins->tlsPtr()); Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset()); @@ -7183,7 +7260,11 @@ CodeGenerator::visitWasmLoadGlobalVar(LWasmLoadGlobalVar* ins) case MIRType::Bool8x16: case MIRType::Bool16x8: case MIRType::Bool32x4: + masm.loadInt32x4(addr, ToFloatRegister(ins->output())); + break; case MIRType::Float32x4: + masm.loadFloat32x4(addr, ToFloatRegister(ins->output())); + break; default: MOZ_CRASH("unexpected type in visitWasmLoadGlobalVar"); } @@ -7195,7 +7276,7 @@ CodeGenerator::visitWasmStoreGlobalVar(LWasmStoreGlobalVar* ins) MWasmStoreGlobalVar* mir = ins->mir(); MIRType type = mir->value()->type(); - MOZ_ASSERT(IsNumberType(type)); + MOZ_ASSERT(IsNumberType(type) || IsSimdType(type)); Register tls = ToRegister(ins->tlsPtr()); Address addr(tls, offsetof(wasm::TlsData, globalArea) + mir->globalDataOffset()); @@ -7222,7 +7303,11 @@ CodeGenerator::visitWasmStoreGlobalVar(LWasmStoreGlobalVar* ins) case MIRType::Bool8x16: case MIRType::Bool16x8: case MIRType::Bool32x4: + masm.storeInt32x4(ToFloatRegister(ins->value()), addr); + break; case MIRType::Float32x4: + masm.storeFloat32x4(ToFloatRegister(ins->value()), addr); + break; default: MOZ_CRASH("unexpected type in visitWasmStoreGlobalVar"); } @@ -10277,6 +10362,7 @@ CodeGenerator::link(JSContext* cx, CompilerConstraintList* constraints) // script, which may have happened off-thread. const JitRealm* jr = gen->realm->jitRealm(); jr->performStubReadBarriers(realmStubsToReadBarrier_); + jr->performSIMDTemplateReadBarriers(simdTemplatesToReadBarrier_); // We finished the new IonScript. Invalidate the current active IonScript, // so we can replace it with this new (probably higher optimized) version. @@ -11500,17 +11586,19 @@ CodeGenerator::visitLoadUnboxedScalar(LLoadUnboxedScalar* lir) const MLoadUnboxedScalar* mir = lir->mir(); Scalar::Type readType = mir->readType(); + unsigned numElems = mir->numElems(); + int width = Scalar::byteSize(mir->storageType()); bool canonicalizeDouble = mir->canonicalizeDoubles(); Label fail; if (lir->index()->isConstant()) { Address source(elements, ToInt32(lir->index()) * width + mir->offsetAdjustment()); - masm.loadFromTypedArray(readType, source, out, temp, &fail, canonicalizeDouble); + masm.loadFromTypedArray(readType, source, out, temp, &fail, canonicalizeDouble, numElems); } else { BaseIndex source(elements, ToRegister(lir->index()), ScaleFromElemWidth(width), mir->offsetAdjustment()); - masm.loadFromTypedArray(readType, source, out, temp, &fail, canonicalizeDouble); + masm.loadFromTypedArray(readType, source, out, temp, &fail, canonicalizeDouble, numElems); } if (fail.used()) @@ -11789,10 +11877,13 @@ CodeGenerator::visitLoadElementFromStateV(LLoadElementFromStateV* lir) template static inline void StoreToTypedArray(MacroAssembler& masm, Scalar::Type writeType, const LAllocation* value, - const T& dest) + const T& dest, unsigned numElems = 0) { - if (writeType == Scalar::Float32 || writeType == Scalar::Float64) { - masm.storeToTypedFloatArray(writeType, ToFloatRegister(value), dest); + if (Scalar::isSimdType(writeType) || + writeType == Scalar::Float32 || + writeType == Scalar::Float64) + { + masm.storeToTypedFloatArray(writeType, ToFloatRegister(value), dest, numElems); } else { if (value->isConstant()) masm.storeToTypedIntArray(writeType, Imm32(ToInt32(value)), dest); @@ -11810,16 +11901,17 @@ CodeGenerator::visitStoreUnboxedScalar(LStoreUnboxedScalar* lir) const MStoreUnboxedScalar* mir = lir->mir(); Scalar::Type writeType = mir->writeType(); + unsigned numElems = mir->numElems(); int width = Scalar::byteSize(mir->storageType()); if (lir->index()->isConstant()) { Address dest(elements, ToInt32(lir->index()) * width + mir->offsetAdjustment()); - StoreToTypedArray(masm, writeType, value, dest); + StoreToTypedArray(masm, writeType, value, dest, numElems); } else { BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width), mir->offsetAdjustment()); - StoreToTypedArray(masm, writeType, value, dest); + StoreToTypedArray(masm, writeType, value, dest, numElems); } } diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 0d4f4d4506a7..85cdffab3593 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -317,9 +317,24 @@ class CodeGenerator final : public CodeGeneratorSpecific PerfSpewer perfSpewer_; #endif + // This integer is a bit mask of all SimdTypeDescr::Type indexes. When a + // MSimdBox instruction is encoded, it might have either been created by + // IonBuilder, or by the Eager Simd Unbox phase. + // + // As the template objects are weak references, the JitRealm is using + // Read Barriers, but such barrier cannot be used during the compilation. To + // work around this issue, the barriers are captured during + // CodeGenerator::link. + // + // Instead of saving the pointers, we just save the index of the Read + // Barriered objects in a bit mask. + uint32_t simdTemplatesToReadBarrier_; + // Bit mask of JitRealm stubs that are to be read-barriered. uint32_t realmStubsToReadBarrier_; + void addSimdTemplateToReadBarrier(SimdType simdType); + #define LIR_OP(op) void visit##op(L##op* ins); LIR_OPCODE_LIST(LIR_OP) #undef LIR_OP diff --git a/js/src/jit/EagerSimdUnbox.cpp b/js/src/jit/EagerSimdUnbox.cpp new file mode 100644 index 000000000000..82bb35d859a0 --- /dev/null +++ b/js/src/jit/EagerSimdUnbox.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/EagerSimdUnbox.h" + +#include "jit/MIR.h" +#include "jit/MIRGenerator.h" +#include "jit/MIRGraph.h" + +namespace js { +namespace jit { + +// Do not optimize any Phi instruction which has conflicting Unbox operations, +// as this might imply some intended polymorphism. +static bool +CanUnboxSimdPhi(const JitRealm* jitRealm, MPhi* phi, SimdType unboxType) +{ + MOZ_ASSERT(phi->type() == MIRType::Object); + + // If we are unboxing, we are more than likely to have boxed this SIMD type + // once in baseline, otherwise, we cannot create a MSimdBox as we have no + // template object to use. + if (!jitRealm->maybeGetSimdTemplateObjectFor(unboxType)) + return false; + + MResumePoint* entry = phi->block()->entryResumePoint(); + MIRType mirType = SimdTypeToMIRType(unboxType); + for (MUseIterator i(phi->usesBegin()), e(phi->usesEnd()); i != e; i++) { + // If we cannot recover the Simd object at the entry of the basic block, + // then we would have to box the content anyways. + if ((*i)->consumer() == entry && !entry->isRecoverableOperand(*i)) + return false; + + if (!(*i)->consumer()->isDefinition()) + continue; + + MDefinition* def = (*i)->consumer()->toDefinition(); + if (def->isSimdUnbox() && def->toSimdUnbox()->type() != mirType) + return false; + } + + return true; +} + +static void +UnboxSimdPhi(const JitRealm* jitRealm, MIRGraph& graph, MPhi* phi, SimdType unboxType) +{ + TempAllocator& alloc = graph.alloc(); + + // Unbox and replace all operands. + for (size_t i = 0, e = phi->numOperands(); i < e; i++) { + MDefinition* op = phi->getOperand(i); + MSimdUnbox* unbox = MSimdUnbox::New(alloc, op, unboxType); + op->block()->insertAtEnd(unbox); + phi->replaceOperand(i, unbox); + } + + // Change the MIRType of the Phi. + MIRType mirType = SimdTypeToMIRType(unboxType); + phi->setResultType(mirType); + + MBasicBlock* phiBlock = phi->block(); + MInstruction* atRecover = phiBlock->safeInsertTop(nullptr, MBasicBlock::IgnoreRecover); + MInstruction* at = phiBlock->safeInsertTop(atRecover); + + // Note, we capture the uses-list now, as new instructions are not visited. + MUseIterator i(phi->usesBegin()), e(phi->usesEnd()); + + // Add a MSimdBox, and replace all the Phi uses with it. + JSObject* templateObject = jitRealm->maybeGetSimdTemplateObjectFor(unboxType); + InlineTypedObject* inlineTypedObject = &templateObject->as(); + MSimdBox* recoverBox = MSimdBox::New(alloc, nullptr, phi, inlineTypedObject, unboxType, gc::DefaultHeap); + recoverBox->setRecoveredOnBailout(); + phiBlock->insertBefore(atRecover, recoverBox); + + MSimdBox* box = nullptr; + while (i != e) { + MUse* use = *i++; + MNode* ins = use->consumer(); + + if ((ins->isDefinition() && ins->toDefinition()->isRecoveredOnBailout()) || + (ins->isResumePoint() && ins->toResumePoint()->isRecoverableOperand(use))) + { + use->replaceProducer(recoverBox); + continue; + } + + if (!box) { + box = MSimdBox::New(alloc, nullptr, phi, inlineTypedObject, unboxType, gc::DefaultHeap); + phiBlock->insertBefore(at, box); + } + + use->replaceProducer(box); + } +} + +bool +EagerSimdUnbox(MIRGenerator* mir, MIRGraph& graph) +{ + const JitRealm* jitRealm = mir->realm->jitRealm(); + for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) { + if (mir->shouldCancel("Eager Simd Unbox")) + return false; + + for (MInstructionReverseIterator ins = block->rbegin(); ins != block->rend(); ins++) { + if (!ins->isSimdUnbox()) + continue; + + MSimdUnbox* unbox = ins->toSimdUnbox(); + if (!unbox->input()->isPhi()) + continue; + + MPhi* phi = unbox->input()->toPhi(); + if (!CanUnboxSimdPhi(jitRealm, phi, unbox->simdType())) + continue; + + UnboxSimdPhi(jitRealm, graph, phi, unbox->simdType()); + } + } + + return true; +} + +} /* namespace jit */ +} /* namespace js */ diff --git a/js/src/jit/EagerSimdUnbox.h b/js/src/jit/EagerSimdUnbox.h new file mode 100644 index 000000000000..382662422996 --- /dev/null +++ b/js/src/jit/EagerSimdUnbox.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file declares eager SIMD unboxing. +#ifndef jit_EagerSimdUnbox_h +#define jit_EagerSimdUnbox_h + +#include "mozilla/Attributes.h" + +namespace js { +namespace jit { + +class MIRGenerator; +class MIRGraph; + +MOZ_MUST_USE bool +EagerSimdUnbox(MIRGenerator* mir, MIRGraph& graph); + +} // namespace jit +} // namespace js + +#endif /* jit_EagerSimdUnbox_h */ diff --git a/js/src/jit/InlinableNatives.h b/js/src/jit/InlinableNatives.h index 17ad8438db90..fec9c9fe0ece 100644 --- a/js/src/jit/InlinableNatives.h +++ b/js/src/jit/InlinableNatives.h @@ -97,6 +97,17 @@ _(ObjectIs) \ _(ObjectToString) \ \ + _(SimdInt32x4) \ + _(SimdUint32x4) \ + _(SimdInt16x8) \ + _(SimdUint16x8) \ + _(SimdInt8x16) \ + _(SimdUint8x16) \ + _(SimdFloat32x4) \ + _(SimdBool32x4) \ + _(SimdBool16x8) \ + _(SimdBool8x16) \ + \ _(TestBailout) \ _(TestAssertFloat32) \ _(TestAssertRecoveredOnBailout) \ diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 0ff0314c9a33..9320c06c0209 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -21,6 +21,7 @@ #include "jit/BaselineJIT.h" #include "jit/CacheIRSpewer.h" #include "jit/CodeGenerator.h" +#include "jit/EagerSimdUnbox.h" #include "jit/EdgeCaseAnalysis.h" #include "jit/EffectiveAddressAnalysis.h" #include "jit/FoldLinearArithConstants.h" @@ -443,6 +444,17 @@ JitRealm::performStubReadBarriers(uint32_t stubsToBarrier) const } } +void +JitRealm::performSIMDTemplateReadBarriers(uint32_t simdTemplatesToBarrier) const +{ + while (simdTemplatesToBarrier) { + auto type = PopNextBitmaskValue(&simdTemplatesToBarrier); + const ReadBarrieredObject& tpl = simdTemplateObjects_[type]; + MOZ_ASSERT(tpl); + tpl.get(); + } +} + bool JitZone::init(JSContext* cx) { @@ -637,6 +649,11 @@ JitRealm::sweep(JS::Realm* realm) if (stub && IsAboutToBeFinalized(&stub)) stub.set(nullptr); } + + for (ReadBarrieredObject& obj : simdTemplateObjects_) { + if (obj && IsAboutToBeFinalized(&obj)) + obj.set(nullptr); + } } void @@ -1467,6 +1484,17 @@ OptimizeMIR(MIRGenerator* mir) return false; } + if (!JitOptions.disableRecoverIns && mir->optimizationInfo().eagerSimdUnboxEnabled()) { + AutoTraceLog log(logger, TraceLogger_EagerSimdUnbox); + if (!EagerSimdUnbox(mir, graph)) + return false; + gs.spewPass("Eager Simd Unbox"); + AssertGraphCoherency(graph); + + if (mir->shouldCancel("Eager Simd Unbox")) + return false; + } + if (mir->optimizationInfo().amaEnabled()) { AutoTraceLog log(logger, TraceLogger_AlignmentMaskAnalysis); AlignmentMaskAnalysis ama(graph); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index eb75e3f26152..0f533a561a9a 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -7902,6 +7902,11 @@ IonBuilder::getElemTryTypedObject(bool* emitted, MDefinition* obj, MDefinition* return Ok(); switch (elemPrediction.kind()) { + case type::Simd: + // FIXME (bug 894105): load into a MIRType::float32x4 etc + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return Ok(); + case type::Struct: case type::Array: return getElemTryComplexElemOfTypedObject(emitted, @@ -8959,6 +8964,11 @@ IonBuilder::setElemTryTypedObject(bool* emitted, MDefinition* obj, return Ok(); switch (elemPrediction.kind()) { + case type::Simd: + // FIXME (bug 894105): store a MIRType::float32x4 etc + trackOptimizationOutcome(TrackedOutcome::GenericFailure); + return Ok(); + case type::Reference: return setElemTryReferenceElemOfTypedObject(emitted, obj, index, objPrediction, value, elemPrediction); @@ -10586,6 +10596,10 @@ IonBuilder::getPropTryTypedObject(bool* emitted, return Ok(); switch (fieldPrediction.kind()) { + case type::Simd: + // FIXME (bug 894104): load into a MIRType::float32x4 etc + return Ok(); + case type::Struct: case type::Array: return getPropTryComplexPropOfTypedObject(emitted, @@ -11728,6 +11742,10 @@ IonBuilder::setPropTryTypedObject(bool* emitted, MDefinition* obj, return Ok(); switch (fieldPrediction.kind()) { + case type::Simd: + // FIXME (bug 894104): store into a MIRType::float32x4 etc + return Ok(); + case type::Reference: return setPropTryReferencePropOfTypedObject(emitted, obj, fieldOffset, value, fieldPrediction, name); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index d013a3fd3def..ca52b5e6a253 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -730,6 +730,51 @@ class IonBuilder InliningResult inlineSetTypedObjectOffset(CallInfo& callInfo); InliningResult inlineConstructTypedObject(CallInfo& callInfo, TypeDescr* target); + // SIMD intrinsics and natives. + InliningResult inlineConstructSimdObject(CallInfo& callInfo, SimdTypeDescr* target); + + // SIMD helpers. + bool canInlineSimd(CallInfo& callInfo, JSNative native, unsigned numArgs, + InlineTypedObject** templateObj); + MDefinition* unboxSimd(MDefinition* ins, SimdType type); + InliningResult boxSimd(CallInfo& callInfo, MDefinition* ins, InlineTypedObject* templateObj); + MDefinition* convertToBooleanSimdLane(MDefinition* scalar); + + InliningResult inlineSimd(CallInfo& callInfo, JSFunction* target, SimdType type); + + InliningResult inlineSimdBinaryArith(CallInfo& callInfo, JSNative native, + MSimdBinaryArith::Operation op, SimdType type); + InliningResult inlineSimdBinaryBitwise(CallInfo& callInfo, JSNative native, + MSimdBinaryBitwise::Operation op, SimdType type); + InliningResult inlineSimdBinarySaturating(CallInfo& callInfo, JSNative native, + MSimdBinarySaturating::Operation op, SimdType type); + InliningResult inlineSimdShift(CallInfo& callInfo, JSNative native, MSimdShift::Operation op, + SimdType type); + InliningResult inlineSimdComp(CallInfo& callInfo, JSNative native, + MSimdBinaryComp::Operation op, SimdType type); + InliningResult inlineSimdUnary(CallInfo& callInfo, JSNative native, + MSimdUnaryArith::Operation op, SimdType type); + InliningResult inlineSimdExtractLane(CallInfo& callInfo, JSNative native, SimdType type); + InliningResult inlineSimdReplaceLane(CallInfo& callInfo, JSNative native, SimdType type); + InliningResult inlineSimdSplat(CallInfo& callInfo, JSNative native, SimdType type); + InliningResult inlineSimdShuffle(CallInfo& callInfo, JSNative native, SimdType type, + unsigned numVectors); + InliningResult inlineSimdCheck(CallInfo& callInfo, JSNative native, SimdType type); + InliningResult inlineSimdConvert(CallInfo& callInfo, JSNative native, bool isCast, + SimdType from, SimdType to); + InliningResult inlineSimdSelect(CallInfo& callInfo, JSNative native, SimdType type); + + bool prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, + MInstruction** elements, MDefinition** index, + Scalar::Type* arrayType); + InliningResult inlineSimdLoad(CallInfo& callInfo, JSNative native, SimdType type, + unsigned numElems); + InliningResult inlineSimdStore(CallInfo& callInfo, JSNative native, SimdType type, + unsigned numElems); + + InliningResult inlineSimdAnyAllTrue(CallInfo& callInfo, bool IsAllTrue, JSNative native, + SimdType type); + // Utility intrinsics. InliningResult inlineIsCallable(CallInfo& callInfo); InliningResult inlineIsConstructor(CallInfo& callInfo); diff --git a/js/src/jit/IonOptimizationLevels.cpp b/js/src/jit/IonOptimizationLevels.cpp index f8426517e02f..67ed2bc14076 100644 --- a/js/src/jit/IonOptimizationLevels.cpp +++ b/js/src/jit/IonOptimizationLevels.cpp @@ -27,6 +27,7 @@ OptimizationInfo::initNormalOptimizationInfo() autoTruncate_ = true; eaa_ = true; + eagerSimdUnbox_ = true; edgeCaseAnalysis_ = true; eliminateRedundantChecks_ = true; inlineInterpreted_ = true; @@ -68,6 +69,7 @@ OptimizationInfo::initWasmOptimizationInfo() ama_ = true; autoTruncate_ = false; + eagerSimdUnbox_ = false; // wasm has no boxing / unboxing. edgeCaseAnalysis_ = false; eliminateRedundantChecks_ = false; scalarReplacement_ = false; // wasm has no objects. diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h index 7c254c7305bf..ddae34de93ce 100644 --- a/js/src/jit/IonOptimizationLevels.h +++ b/js/src/jit/IonOptimizationLevels.h @@ -65,6 +65,9 @@ class OptimizationInfo // Toggles whether native scripts get inlined. bool inlineNative_; + // Toggles whether eager unboxing of SIMD is used. + bool eagerSimdUnbox_; + // Toggles whether global value numbering is used. bool gvn_; @@ -154,6 +157,7 @@ class OptimizationInfo eliminateRedundantChecks_(false), inlineInterpreted_(false), inlineNative_(false), + eagerSimdUnbox_(false), gvn_(false), licm_(false), rangeAnalysis_(false), @@ -194,6 +198,10 @@ class OptimizationInfo uint32_t compilerWarmUpThreshold(JSScript* script, jsbytecode* pc = nullptr) const; + bool eagerSimdUnboxEnabled() const { + return eagerSimdUnbox_ && !JitOptions.disableEagerSimdUnbox; + } + bool gvnEnabled() const { return gvn_ && !JitOptions.disableGvn; } diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index c094b6c8beb4..79f5bc7f0195 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -104,6 +104,9 @@ enum BailoutKind Bailout_NonStringInput, Bailout_NonSymbolInput, + // SIMD Unbox expects a given type, bails out if it doesn't match. + Bailout_UnexpectedSimdInput, + // Atomic operations require shared memory, bail out if the typed array // maps unshared memory. Bailout_NonSharedTypedArrayInput, @@ -205,6 +208,8 @@ BailoutKindString(BailoutKind kind) return "Bailout_NonStringInput"; case Bailout_NonSymbolInput: return "Bailout_NonSymbolInput"; + case Bailout_UnexpectedSimdInput: + return "Bailout_UnexpectedSimdInput"; case Bailout_NonSharedTypedArrayInput: return "Bailout_NonSharedTypedArrayInput"; case Bailout_Debugger: @@ -247,19 +252,6 @@ static const uint32_t VECTOR_SCALE_BITS = 3; static const uint32_t VECTOR_SCALE_SHIFT = ELEMENT_TYPE_BITS + ELEMENT_TYPE_SHIFT; static const uint32_t VECTOR_SCALE_MASK = (1 << VECTOR_SCALE_BITS) - 1; -// The integer SIMD types have a lot of operations that do the exact same thing -// for signed and unsigned integer types. Sometimes it is simpler to treat -// signed and unsigned integer SIMD types as the same type, using a SimdSign to -// distinguish the few cases where there is a difference. -enum class SimdSign { - // Signedness is not applicable to this type. (i.e., Float or Bool). - NotApplicable, - // Treat as an unsigned integer with a range 0 .. 2^N-1. - Unsigned, - // Treat as a signed integer in two's complement encoding. - Signed, -}; - class SimdConstant { public: enum Type { @@ -465,6 +457,39 @@ IsSimdType(MIRType type) return ((unsigned(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK) != 0; } +// Returns the number of vector elements (hereby called "length") for a given +// SIMD kind. It is the Y part of the name "Foo x Y". +static inline unsigned +SimdTypeToLength(MIRType type) +{ + MOZ_ASSERT(IsSimdType(type)); + return 1 << ((unsigned(type) >> VECTOR_SCALE_SHIFT) & VECTOR_SCALE_MASK); +} + +// Get the type of the individual lanes in a SIMD type. +// For example, Int32x4 -> Int32, Float32x4 -> Float32 etc. +static inline MIRType +SimdTypeToLaneType(MIRType type) +{ + MOZ_ASSERT(IsSimdType(type)); + static_assert(unsigned(MIRType::Last) <= ELEMENT_TYPE_MASK, + "ELEMENT_TYPE_MASK should be larger than the last MIRType"); + return MIRType((unsigned(type) >> ELEMENT_TYPE_SHIFT) & ELEMENT_TYPE_MASK); +} + +// Get the type expected when inserting a lane into a SIMD type. +// This is the argument type expected by the MSimdValue constructors as well as +// MSimdSplat and MSimdInsertElement. +static inline MIRType +SimdTypeToLaneArgumentType(MIRType type) +{ + MIRType laneType = SimdTypeToLaneType(type); + + // Boolean lanes should be pre-converted to an Int32 with the values 0 or -1. + // All other lane types are inserted directly. + return laneType == MIRType::Boolean ? MIRType::Int32 : laneType; +} + static inline MIRType MIRTypeFromValueType(JSValueType type) { @@ -664,6 +689,24 @@ IsNullOrUndefined(MIRType type) return type == MIRType::Null || type == MIRType::Undefined; } +static inline bool +IsFloatingPointSimdType(MIRType type) +{ + return type == MIRType::Float32x4; +} + +static inline bool +IsIntegerSimdType(MIRType type) +{ + return IsSimdType(type) && SimdTypeToLaneType(type) == MIRType::Int32; +} + +static inline bool +IsBooleanSimdType(MIRType type) +{ + return IsSimdType(type) && SimdTypeToLaneType(type) == MIRType::Boolean; +} + static inline bool IsMagicType(MIRType type) { @@ -692,10 +735,18 @@ ScalarTypeToMIRType(Scalar::Type type) return MIRType::Float32; case Scalar::Float64: return MIRType::Double; + case Scalar::Float32x4: + return MIRType::Float32x4; + case Scalar::Int8x16: + return MIRType::Int8x16; + case Scalar::Int16x8: + return MIRType::Int16x8; + case Scalar::Int32x4: + return MIRType::Int32x4; case Scalar::MaxTypedArrayViewType: break; } - MOZ_CRASH("unexpected kind"); + MOZ_CRASH("unexpected SIMD kind"); } static inline unsigned @@ -713,10 +764,17 @@ ScalarTypeToLength(Scalar::Type type) case Scalar::Float64: case Scalar::Uint8Clamped: return 1; + case Scalar::Float32x4: + case Scalar::Int32x4: + return 4; + case Scalar::Int16x8: + return 8; + case Scalar::Int8x16: + return 16; case Scalar::MaxTypedArrayViewType: break; } - MOZ_CRASH("unexpected kind"); + MOZ_CRASH("unexpected SIMD kind"); } static inline const char* diff --git a/js/src/jit/JSJitFrameIter.h b/js/src/jit/JSJitFrameIter.h index 07b1013fbe9b..27085dde1914 100644 --- a/js/src/jit/JSJitFrameIter.h +++ b/js/src/jit/JSJitFrameIter.h @@ -407,7 +407,9 @@ struct MaybeReadFallback } }; + class RResumePoint; +class RSimdBox; // Reads frame information in snapshot-encoding order (that is, outermost frame // to innermost frame). @@ -469,6 +471,7 @@ class SnapshotIterator void warnUnreadableAllocation(); private: + friend class RSimdBox; const FloatRegisters::RegisterContent* floatAllocationPointer(const RValueAllocation& a) const; public: diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index da61a4ab9a83..b7f5230445e2 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -86,6 +86,9 @@ DefaultJitOptions::DefaultJitOptions() // Toggles whether Effective Address Analysis is globally disabled. SET_DEFAULT(disableEaa, false); + // Toggle whether eager simd unboxing is globally disabled. + SET_DEFAULT(disableEagerSimdUnbox, false); + // Toggles whether Edge Case Analysis is gobally disabled. SET_DEFAULT(disableEdgeCaseAnalysis, false); diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h index ac4696f3a7d5..ed645eaf7305 100644 --- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -50,6 +50,7 @@ struct DefaultJitOptions bool disableInlineBacktracking; bool disableAma; bool disableEaa; + bool disableEagerSimdUnbox; bool disableEdgeCaseAnalysis; bool disableGvn; bool disableInlining; diff --git a/js/src/jit/JitRealm.h b/js/src/jit/JitRealm.h index fb859bf8f729..e70aef63a317 100644 --- a/js/src/jit/JitRealm.h +++ b/js/src/jit/JitRealm.h @@ -507,6 +507,10 @@ class JitRealm mozilla::EnumeratedArray stubs_; + // The same approach is taken for SIMD template objects. + + mozilla::EnumeratedArray simdTemplateObjects_; + JitCode* generateStringConcatStub(JSContext* cx); JitCode* generateRegExpMatcherStub(JSContext* cx); JitCode* generateRegExpSearcherStub(JSContext* cx); @@ -519,6 +523,23 @@ class JitRealm } public: + JSObject* getSimdTemplateObjectFor(JSContext* cx, Handle descr) { + ReadBarrieredObject& tpl = simdTemplateObjects_[descr->type()]; + if (!tpl) + tpl.set(TypedObject::createZeroed(cx, descr, gc::TenuredHeap)); + return tpl.get(); + } + + JSObject* maybeGetSimdTemplateObjectFor(SimdType type) const { + // This function is used by Eager Simd Unbox phase which can run + // off-thread, so we cannot use the usual read barrier. For more + // information, see the comment above + // CodeGenerator::simdRefreshTemplatesDuringLink_. + + MOZ_ASSERT(CurrentThreadIsIonCompiling()); + return simdTemplateObjects_[type].unbarrieredGet(); + } + JitCode* getStubCode(uint32_t key) { ICStubCodeMap::Ptr p = stubCodes_->lookup(key); if (p) @@ -599,13 +620,15 @@ class JitRealm return stubs_[RegExpTester]; } - // Perform the necessary read barriers on stubs described by the bitmasks - // passed in. This function can only be called from the main thread. + // Perform the necessary read barriers on stubs and SIMD template object + // described by the bitmasks passed in. This function can only be called + // from the main thread. // - // The stub pointers must still be valid by the time these methods are - // called. This is arranged by cancelling off-thread Ion compilation at the - // start of GC and at the start of sweeping. + // The stub and template object pointers must still be valid by the time + // these methods are called. This is arranged by cancelling off-thread Ion + // compilation at the start of GC and at the start of sweeping. void performStubReadBarriers(uint32_t stubsToBarrier) const; + void performSIMDTemplateReadBarriers(uint32_t simdTemplatesToBarrier) const; size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 641dfdbc9eb2..96041d2b4686 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -3689,7 +3689,8 @@ LIRGenerator::visitLoadUnboxedScalar(MLoadUnboxedScalar* ins) const LUse elements = useRegister(ins->elements()); const LAllocation index = useRegisterOrConstant(ins->index()); - MOZ_ASSERT(IsNumberType(ins->type()) || ins->type() == MIRType::Boolean); + MOZ_ASSERT(IsNumberType(ins->type()) || IsSimdType(ins->type()) || + ins->type() == MIRType::Boolean); // We need a temp register for Uint32Array with known double result. LDefinition tempDef = LDefinition::BogusTemp(); @@ -3770,7 +3771,12 @@ LIRGenerator::visitStoreUnboxedScalar(MStoreUnboxedScalar* ins) MOZ_ASSERT(IsValidElementsType(ins->elements(), ins->offsetAdjustment())); MOZ_ASSERT(ins->index()->type() == MIRType::Int32); - if (ins->isFloatWrite()) { + if (ins->isSimdWrite()) { + MOZ_ASSERT_IF(ins->writeType() == Scalar::Float32x4, ins->value()->type() == MIRType::Float32x4); + MOZ_ASSERT_IF(ins->writeType() == Scalar::Int8x16, ins->value()->type() == MIRType::Int8x16); + MOZ_ASSERT_IF(ins->writeType() == Scalar::Int16x8, ins->value()->type() == MIRType::Int16x8); + MOZ_ASSERT_IF(ins->writeType() == Scalar::Int32x4, ins->value()->type() == MIRType::Int32x4); + } else if (ins->isFloatWrite()) { MOZ_ASSERT_IF(ins->writeType() == Scalar::Float32, ins->value()->type() == MIRType::Float32); MOZ_ASSERT_IF(ins->writeType() == Scalar::Float64, ins->value()->type() == MIRType::Double); } else { @@ -4704,7 +4710,7 @@ LIRGenerator::visitWasmParameter(MWasmParameter* ins) #endif ); } else { - MOZ_ASSERT(IsNumberType(ins->type())); + MOZ_ASSERT(IsNumberType(ins->type()) || IsSimdType(ins->type())); defineFixed(new(alloc()) LWasmParameter, ins, LArgument(abi.offsetFromArgBase())); } } @@ -4724,6 +4730,8 @@ LIRGenerator::visitWasmReturn(MWasmReturn* ins) lir->setOperand(0, useFixed(rval, ReturnFloat32Reg)); else if (rval->type() == MIRType::Double) lir->setOperand(0, useFixed(rval, ReturnDoubleReg)); + else if (IsSimdType(rval->type())) + lir->setOperand(0, useFixed(rval, ReturnSimd128Reg)); else if (rval->type() == MIRType::Int32) lir->setOperand(0, useFixed(rval, ReturnReg)); else @@ -4743,7 +4751,7 @@ LIRGenerator::visitWasmStackArg(MWasmStackArg* ins) { if (ins->arg()->type() == MIRType::Int64) { add(new(alloc()) LWasmStackArgI64(useInt64RegisterOrConstantAtStart(ins->arg())), ins); - } else if (IsFloatingPointType(ins->arg()->type())) { + } else if (IsFloatingPointType(ins->arg()->type()) || IsSimdType(ins->arg()->type())) { MOZ_ASSERT(!ins->arg()->isEmittedAtUses()); add(new(alloc()) LWasmStackArg(useRegisterAtStart(ins->arg())), ins); } else { @@ -4878,6 +4886,217 @@ LIRGenerator::visitRecompileCheck(MRecompileCheck* ins) assignSafepoint(lir, ins); } +void +LIRGenerator::visitSimdBox(MSimdBox* ins) +{ + MOZ_ASSERT(IsSimdType(ins->input()->type())); + LUse in = useRegister(ins->input()); + LSimdBox* lir = new(alloc()) LSimdBox(in, temp()); + define(lir, ins); + assignSafepoint(lir, ins); +} + +void +LIRGenerator::visitSimdUnbox(MSimdUnbox* ins) +{ + MOZ_ASSERT(ins->input()->type() == MIRType::Object); + MOZ_ASSERT(IsSimdType(ins->type())); + LUse in = useRegister(ins->input()); + LSimdUnbox* lir = new(alloc()) LSimdUnbox(in, temp()); + assignSnapshot(lir, Bailout_UnexpectedSimdInput); + define(lir, ins); +} + +void +LIRGenerator::visitSimdConstant(MSimdConstant* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + + switch (ins->type()) { + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + define(new(alloc()) LSimd128Int(), ins); + break; + case MIRType::Float32x4: + define(new(alloc()) LSimd128Float(), ins); + break; + default: + MOZ_CRASH("Unknown SIMD kind when generating constant"); + } +} + +void +LIRGenerator::visitSimdConvert(MSimdConvert* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + MDefinition* input = ins->input(); + LUse use = useRegister(input); + if (ins->type() == MIRType::Int32x4) { + MOZ_ASSERT(input->type() == MIRType::Float32x4); + switch (ins->signedness()) { + case SimdSign::Signed: { + LFloat32x4ToInt32x4* lir = new(alloc()) LFloat32x4ToInt32x4(use, temp()); + if (!gen->compilingWasm()) + assignSnapshot(lir, Bailout_BoundsCheck); + define(lir, ins); + break; + } + case SimdSign::Unsigned: { + LFloat32x4ToUint32x4* lir = + new (alloc()) LFloat32x4ToUint32x4(use, temp(), temp(LDefinition::SIMD128INT)); + if (!gen->compilingWasm()) + assignSnapshot(lir, Bailout_BoundsCheck); + define(lir, ins); + break; + } + default: + MOZ_CRASH("Unexpected SimdConvert sign"); + } + } else if (ins->type() == MIRType::Float32x4) { + MOZ_ASSERT(input->type() == MIRType::Int32x4); + MOZ_ASSERT(ins->signedness() == SimdSign::Signed, "Unexpected SimdConvert sign"); + define(new(alloc()) LInt32x4ToFloat32x4(use), ins); + } else { + MOZ_CRASH("Unknown SIMD kind when generating constant"); + } +} + +void +LIRGenerator::visitSimdReinterpretCast(MSimdReinterpretCast* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type()) && IsSimdType(ins->input()->type())); + MDefinition* input = ins->input(); + LUse use = useRegisterAtStart(input); + // :TODO: (Bug 1132894) We have to allocate a different register as redefine + // and/or defineReuseInput are not yet capable of reusing the same register + // with a different register type. + define(new(alloc()) LSimdReinterpretCast(use), ins); +} + +void +LIRGenerator::visitSimdAllTrue(MSimdAllTrue* ins) +{ + MDefinition* input = ins->input(); + MOZ_ASSERT(IsBooleanSimdType(input->type())); + + LUse use = useRegisterAtStart(input); + define(new(alloc()) LSimdAllTrue(use), ins); +} + +void +LIRGenerator::visitSimdAnyTrue(MSimdAnyTrue* ins) +{ + MDefinition* input = ins->input(); + MOZ_ASSERT(IsBooleanSimdType(input->type())); + + LUse use = useRegisterAtStart(input); + define(new(alloc()) LSimdAnyTrue(use), ins); +} + +void +LIRGenerator::visitSimdUnaryArith(MSimdUnaryArith* ins) +{ + MOZ_ASSERT(IsSimdType(ins->input()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + + // Cannot be at start, as the ouput is used as a temporary to store values. + LUse in = use(ins->input()); + + switch (ins->type()) { + case MIRType::Int8x16: + case MIRType::Bool8x16: + define(new (alloc()) LSimdUnaryArithIx16(in), ins); + break; + case MIRType::Int16x8: + case MIRType::Bool16x8: + define(new (alloc()) LSimdUnaryArithIx8(in), ins); + break; + case MIRType::Int32x4: + case MIRType::Bool32x4: + define(new (alloc()) LSimdUnaryArithIx4(in), ins); + break; + case MIRType::Float32x4: + define(new (alloc()) LSimdUnaryArithFx4(in), ins); + break; + default: + MOZ_CRASH("Unknown SIMD kind for unary operation"); + } +} + +void +LIRGenerator::visitSimdBinaryComp(MSimdBinaryComp* ins) +{ + MOZ_ASSERT(IsSimdType(ins->lhs()->type())); + MOZ_ASSERT(IsSimdType(ins->rhs()->type())); + MOZ_ASSERT(IsBooleanSimdType(ins->type())); + + if (ShouldReorderCommutative(ins->lhs(), ins->rhs(), ins)) + ins->reverse(); + + switch (ins->specialization()) { + case MIRType::Int8x16: { + MOZ_ASSERT(ins->signedness() == SimdSign::Signed); + LSimdBinaryCompIx16* add = new (alloc()) LSimdBinaryCompIx16(); + lowerForFPU(add, ins, ins->lhs(), ins->rhs()); + return; + } + case MIRType::Int16x8: { + MOZ_ASSERT(ins->signedness() == SimdSign::Signed); + LSimdBinaryCompIx8* add = new (alloc()) LSimdBinaryCompIx8(); + lowerForFPU(add, ins, ins->lhs(), ins->rhs()); + return; + } + case MIRType::Int32x4: { + MOZ_ASSERT(ins->signedness() == SimdSign::Signed); + LSimdBinaryCompIx4* add = new (alloc()) LSimdBinaryCompIx4(); + lowerForCompIx4(add, ins, ins->lhs(), ins->rhs()); + return; + } + case MIRType::Float32x4: { + MOZ_ASSERT(ins->signedness() == SimdSign::NotApplicable); + LSimdBinaryCompFx4* add = new (alloc()) LSimdBinaryCompFx4(); + lowerForCompFx4(add, ins, ins->lhs(), ins->rhs()); + return; + } + default: + MOZ_CRASH("Unknown compare type when comparing values"); + } +} + +void +LIRGenerator::visitSimdBinaryBitwise(MSimdBinaryBitwise* ins) +{ + MOZ_ASSERT(IsSimdType(ins->lhs()->type())); + MOZ_ASSERT(IsSimdType(ins->rhs()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + + MDefinition* lhs = ins->lhs(); + MDefinition* rhs = ins->rhs(); + ReorderCommutative(&lhs, &rhs, ins); + LSimdBinaryBitwise* lir = new(alloc()) LSimdBinaryBitwise; + lowerForFPU(lir, ins, lhs, rhs); +} + +void +LIRGenerator::visitSimdShift(MSimdShift* ins) +{ + MOZ_ASSERT(IsIntegerSimdType(ins->type())); + MOZ_ASSERT(ins->lhs()->type() == ins->type()); + MOZ_ASSERT(ins->rhs()->type() == MIRType::Int32); + + LUse vector = useRegisterAtStart(ins->lhs()); + LAllocation value = useRegisterOrConstant(ins->rhs()); + // We need a temp register to mask the shift amount, but not if the shift + // amount is a constant. + LDefinition tempReg = value.isConstant() ? LDefinition::BogusTemp() : temp(); + LSimdShift* lir = new(alloc()) LSimdShift(vector, value, tempReg); + defineReuseInput(lir, ins, 0); +} + void LIRGenerator::visitLexicalCheck(MLexicalCheck* ins) { diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 91f70afab0c6..b29035e01132 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -15,6 +15,7 @@ #include "builtin/intl/PluralRules.h" #include "builtin/intl/RelativeTimeFormat.h" #include "builtin/MapObject.h" +#include "builtin/SIMDConstants.h" #include "builtin/String.h" #include "builtin/TestingFunctions.h" #include "builtin/TypedObject.h" @@ -262,6 +263,28 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) case InlinableNative::ObjectToString: return inlineObjectToString(callInfo); + // SIMD natives. + case InlinableNative::SimdInt32x4: + return inlineSimd(callInfo, target, SimdType::Int32x4); + case InlinableNative::SimdUint32x4: + return inlineSimd(callInfo, target, SimdType::Uint32x4); + case InlinableNative::SimdInt16x8: + return inlineSimd(callInfo, target, SimdType::Int16x8); + case InlinableNative::SimdUint16x8: + return inlineSimd(callInfo, target, SimdType::Uint16x8); + case InlinableNative::SimdInt8x16: + return inlineSimd(callInfo, target, SimdType::Int8x16); + case InlinableNative::SimdUint8x16: + return inlineSimd(callInfo, target, SimdType::Uint8x16); + case InlinableNative::SimdFloat32x4: + return inlineSimd(callInfo, target, SimdType::Float32x4); + case InlinableNative::SimdBool32x4: + return inlineSimd(callInfo, target, SimdType::Bool32x4); + case InlinableNative::SimdBool16x8: + return inlineSimd(callInfo, target, SimdType::Bool16x8); + case InlinableNative::SimdBool8x16: + return inlineSimd(callInfo, target, SimdType::Bool8x16); + // Testing functions. case InlinableNative::TestBailout: return inlineBailout(callInfo); @@ -456,6 +479,9 @@ IonBuilder::inlineNonFunctionCall(CallInfo& callInfo, JSObject* target) if (callInfo.constructing() && target->constructHook() == TypedObject::construct) return inlineConstructTypedObject(callInfo, &target->as()); + if (!callInfo.constructing() && target->callHook() == SimdTypeDescr::call) + return inlineConstructSimdObject(callInfo, &target->as()); + return InliningStatus_NotInlined; } @@ -3771,6 +3797,766 @@ IonBuilder::inlineConstructTypedObject(CallInfo& callInfo, TypeDescr* descr) return InliningStatus_Inlined; } +// Main entry point for SIMD inlining. +// When the controlling simdType is an integer type, sign indicates whether the lanes should +// be treated as signed or unsigned integers. +IonBuilder::InliningResult +IonBuilder::inlineSimd(CallInfo& callInfo, JSFunction* target, SimdType type) +{ + if (!JitSupportsSimd()) { + trackOptimizationOutcome(TrackedOutcome::NoSimdJitSupport); + return InliningStatus_NotInlined; + } + + JSNative native = target->native(); + SimdOperation simdOp = SimdOperation(target->jitInfo()->nativeOp); + + switch(simdOp) { + case SimdOperation::Constructor: + // SIMD constructor calls are handled via inlineNonFunctionCall(), so + // they won't show up here where target is required to be a JSFunction. + // See also inlineConstructSimdObject(). + MOZ_CRASH("SIMD constructor call not expected."); + case SimdOperation::Fn_check: + return inlineSimdCheck(callInfo, native, type); + case SimdOperation::Fn_splat: + return inlineSimdSplat(callInfo, native, type); + case SimdOperation::Fn_extractLane: + return inlineSimdExtractLane(callInfo, native, type); + case SimdOperation::Fn_replaceLane: + return inlineSimdReplaceLane(callInfo, native, type); + case SimdOperation::Fn_select: + return inlineSimdSelect(callInfo, native, type); + case SimdOperation::Fn_swizzle: + return inlineSimdShuffle(callInfo, native, type, 1); + case SimdOperation::Fn_shuffle: + return inlineSimdShuffle(callInfo, native, type, 2); + + // Unary arithmetic. + case SimdOperation::Fn_abs: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::abs, type); + case SimdOperation::Fn_neg: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::neg, type); + case SimdOperation::Fn_not: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::not_, type); + case SimdOperation::Fn_reciprocalApproximation: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::reciprocalApproximation, + type); + case SimdOperation::Fn_reciprocalSqrtApproximation: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::reciprocalSqrtApproximation, + type); + case SimdOperation::Fn_sqrt: + return inlineSimdUnary(callInfo, native, MSimdUnaryArith::sqrt, type); + + // Binary arithmetic. + case SimdOperation::Fn_add: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_add, type); + case SimdOperation::Fn_sub: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_sub, type); + case SimdOperation::Fn_mul: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_mul, type); + case SimdOperation::Fn_div: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_div, type); + case SimdOperation::Fn_max: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_max, type); + case SimdOperation::Fn_min: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_min, type); + case SimdOperation::Fn_maxNum: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_maxNum, type); + case SimdOperation::Fn_minNum: + return inlineSimdBinaryArith(callInfo, native, MSimdBinaryArith::Op_minNum, type); + + // Binary saturating. + case SimdOperation::Fn_addSaturate: + return inlineSimdBinarySaturating(callInfo, native, MSimdBinarySaturating::add, type); + case SimdOperation::Fn_subSaturate: + return inlineSimdBinarySaturating(callInfo, native, MSimdBinarySaturating::sub, type); + + // Binary bitwise. + case SimdOperation::Fn_and: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::and_, type); + case SimdOperation::Fn_or: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::or_, type); + case SimdOperation::Fn_xor: + return inlineSimdBinaryBitwise(callInfo, native, MSimdBinaryBitwise::xor_, type); + + // Shifts. + case SimdOperation::Fn_shiftLeftByScalar: + return inlineSimdShift(callInfo, native, MSimdShift::lsh, type); + case SimdOperation::Fn_shiftRightByScalar: + return inlineSimdShift(callInfo, native, MSimdShift::rshForSign(GetSimdSign(type)), type); + + // Boolean unary. + case SimdOperation::Fn_allTrue: + return inlineSimdAnyAllTrue(callInfo, /* IsAllTrue= */true, native, type); + case SimdOperation::Fn_anyTrue: + return inlineSimdAnyAllTrue(callInfo, /* IsAllTrue= */false, native, type); + + // Comparisons. + case SimdOperation::Fn_lessThan: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::lessThan, type); + case SimdOperation::Fn_lessThanOrEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::lessThanOrEqual, type); + case SimdOperation::Fn_equal: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::equal, type); + case SimdOperation::Fn_notEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::notEqual, type); + case SimdOperation::Fn_greaterThan: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::greaterThan, type); + case SimdOperation::Fn_greaterThanOrEqual: + return inlineSimdComp(callInfo, native, MSimdBinaryComp::greaterThanOrEqual, type); + + // Int <-> Float conversions. + case SimdOperation::Fn_fromInt32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Int32x4, type); + case SimdOperation::Fn_fromUint32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Uint32x4, type); + case SimdOperation::Fn_fromFloat32x4: + return inlineSimdConvert(callInfo, native, false, SimdType::Float32x4, type); + + // Load/store. + case SimdOperation::Fn_load: + return inlineSimdLoad(callInfo, native, type, GetSimdLanes(type)); + case SimdOperation::Fn_load1: + return inlineSimdLoad(callInfo, native, type, 1); + case SimdOperation::Fn_load2: + return inlineSimdLoad(callInfo, native, type, 2); + case SimdOperation::Fn_load3: + return inlineSimdLoad(callInfo, native, type, 3); + case SimdOperation::Fn_store: + return inlineSimdStore(callInfo, native, type, GetSimdLanes(type)); + case SimdOperation::Fn_store1: + return inlineSimdStore(callInfo, native, type, 1); + case SimdOperation::Fn_store2: + return inlineSimdStore(callInfo, native, type, 2); + case SimdOperation::Fn_store3: + return inlineSimdStore(callInfo, native, type, 3); + + // Bitcasts. One for each type with a memory representation. + case SimdOperation::Fn_fromInt32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int32x4, type); + case SimdOperation::Fn_fromUint32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint32x4, type); + case SimdOperation::Fn_fromInt16x8Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int16x8, type); + case SimdOperation::Fn_fromUint16x8Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint16x8, type); + case SimdOperation::Fn_fromInt8x16Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Int8x16, type); + case SimdOperation::Fn_fromUint8x16Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Uint8x16, type); + case SimdOperation::Fn_fromFloat32x4Bits: + return inlineSimdConvert(callInfo, native, true, SimdType::Float32x4, type); + case SimdOperation::Fn_fromFloat64x2Bits: + return InliningStatus_NotInlined; + } + + MOZ_CRASH("Unexpected SIMD opcode"); +} + +// The representation of boolean SIMD vectors is the same as the corresponding +// integer SIMD vectors with -1 lanes meaning true and 0 lanes meaning false. +// +// Functions that set the value of a boolean vector lane work by applying +// ToBoolean on the input argument, so they accept any argument type, just like +// the MNot and MTest instructions. +// +// Convert any scalar value into an appropriate SIMD lane value: An Int32 value +// that is either 0 for false or -1 for true. +MDefinition* +IonBuilder::convertToBooleanSimdLane(MDefinition* scalar) +{ + MSub* result; + + if (scalar->type() == MIRType::Boolean) { + // The input scalar is already a boolean with the int32 values 0 / 1. + // Compute result = 0 - scalar. + result = MSub::New(alloc(), constant(Int32Value(0)), scalar); + } else { + // For any other type, let MNot handle the conversion to boolean. + // Compute result = !scalar - 1. + MNot* inv = MNot::New(alloc(), scalar); + current->add(inv); + result = MSub::New(alloc(), inv, constant(Int32Value(1))); + } + + result->setInt32Specialization(); + current->add(result); + return result; +} + +IonBuilder::InliningResult +IonBuilder::inlineConstructSimdObject(CallInfo& callInfo, SimdTypeDescr* descr) +{ + if (!JitSupportsSimd()) { + trackOptimizationOutcome(TrackedOutcome::NoSimdJitSupport); + return InliningStatus_NotInlined; + } + + // Generic constructor of SIMD valuesX4. + MIRType simdType; + if (!MaybeSimdTypeToMIRType(descr->type(), &simdType)) { + trackOptimizationOutcome(TrackedOutcome::SimdTypeNotOptimized); + return InliningStatus_NotInlined; + } + + // Take the templateObject out of Baseline ICs, such that we can box + // SIMD value type in the same kind of objects. + MOZ_ASSERT(InlineTypedObject::canAccommodateType(descr)); + MOZ_ASSERT(descr->getClass() == &SimdTypeDescr::class_, + "getTemplateObjectForSimdCtor needs an update"); + + JSObject* templateObject = inspector->getTemplateObjectForSimdCtor(pc, descr->type()); + if (!templateObject) + return InliningStatus_NotInlined; + + // The previous assertion ensures this will never fail if we were able to + // allocate a templateObject in Baseline. + InlineTypedObject* inlineTypedObject = &templateObject->as(); + MOZ_ASSERT(&inlineTypedObject->typeDescr() == descr); + + // When there are missing arguments, provide a default value + // containing the coercion of 'undefined' to the right type. + MConstant* defVal = nullptr; + MIRType laneType = SimdTypeToLaneType(simdType); + unsigned lanes = SimdTypeToLength(simdType); + if (lanes != 4 || callInfo.argc() < lanes) { + if (laneType == MIRType::Int32 || laneType == MIRType::Boolean) { + // The default lane for a boolean vector is |false|, but + // |MSimdSplat|, |MSimdValueX4|, and |MSimdInsertElement| all + // require an Int32 argument with the value 0 or 01 to initialize a + // boolean lane. See also convertToBooleanSimdLane() which is + // idempotent with a 0 argument after constant folding. + defVal = constant(Int32Value(0)); + } else if (laneType == MIRType::Double) { + defVal = constant(DoubleNaNValue()); + } else { + MOZ_ASSERT(laneType == MIRType::Float32); + defVal = MConstant::NewFloat32(alloc(), JS::GenericNaN()); + current->add(defVal); + } + } + + MInstruction *values = nullptr; + + // Use the MSimdValueX4 constructor for X4 vectors. + if (lanes == 4) { + MDefinition* lane[4]; + for (unsigned i = 0; i < 4; i++) + lane[i] = callInfo.getArgWithDefault(i, defVal); + + // Convert boolean lanes into Int32 0 / -1. + if (laneType == MIRType::Boolean) { + for (unsigned i = 0; i < 4; i++) + lane[i] = convertToBooleanSimdLane(lane[i]); + } + + values = MSimdValueX4::New(alloc(), simdType, lane[0], lane[1], lane[2], lane[3]); + current->add(values); + } else { + // For general constructor calls, start from splat(defVal), insert one + // lane at a time. + values = MSimdSplat::New(alloc(), defVal, simdType); + current->add(values); + + // Stop early if constructor doesn't have enough arguments. These lanes + // then get the default value. + if (callInfo.argc() < lanes) + lanes = callInfo.argc(); + + for (unsigned i = 0; i < lanes; i++) { + MDefinition* lane = callInfo.getArg(i); + if (laneType == MIRType::Boolean) + lane = convertToBooleanSimdLane(lane); + values = MSimdInsertElement::New(alloc(), values, lane, i); + current->add(values); + } + } + + MSimdBox* obj = MSimdBox::New(alloc(), constraints(), values, inlineTypedObject, descr->type(), + inlineTypedObject->group()->initialHeap(constraints())); + current->add(obj); + current->push(obj); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +bool +IonBuilder::canInlineSimd(CallInfo& callInfo, JSNative native, unsigned numArgs, + InlineTypedObject** templateObj) +{ + if (callInfo.argc() != numArgs) + return false; + + JSObject* templateObject = inspector->getTemplateObjectForNative(pc, native); + if (!templateObject) + return false; + + *templateObj = &templateObject->as(); + return true; +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdCheck(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + // Unboxing checks the SIMD object type and throws a TypeError if it doesn't + // match type. + MDefinition *arg = unboxSimd(callInfo.getArg(0), type); + + // Create an unbox/box pair, expecting the box to be optimized away if + // anyone use the return value from this check() call. This is what you want + // for code like this: + // + // function f(x) { + // x = Int32x4.check(x) + // for(...) { + // y = Int32x4.add(x, ...) + // } + // + // The unboxing of x happens as early as possible, and only once. + return boxSimd(callInfo, arg, templateObj); +} + +// Given a value or object, insert a dynamic check that this is a SIMD object of +// the required SimdType, and unbox it into the corresponding SIMD MIRType. +// +// This represents the standard type checking that all the SIMD operations +// perform on their arguments. +MDefinition* +IonBuilder::unboxSimd(MDefinition* ins, SimdType type) +{ + // Trivial optimization: If ins is a MSimdBox of the same SIMD type, there + // is no way the unboxing could fail, and we can skip it altogether. + // This is the same thing MSimdUnbox::foldsTo() does, but we can save the + // memory allocation here. + if (ins->isSimdBox()) { + MSimdBox* box = ins->toSimdBox(); + if (box->simdType() == type) { + MDefinition* value = box->input(); + MOZ_ASSERT(value->type() == SimdTypeToMIRType(type)); + return value; + } + } + + MSimdUnbox* unbox = MSimdUnbox::New(alloc(), ins, type); + current->add(unbox); + return unbox; +} + +IonBuilder::InliningResult +IonBuilder::boxSimd(CallInfo& callInfo, MDefinition* ins, InlineTypedObject* templateObj) +{ + SimdType simdType = templateObj->typeDescr().as().type(); + MSimdBox* obj = MSimdBox::New(alloc(), constraints(), ins, templateObj, simdType, + templateObj->group()->initialHeap(constraints())); + + // In some cases, ins has already been added to current. + if (!ins->block() && ins->isInstruction()) + current->add(ins->toInstruction()); + current->add(obj); + current->push(obj); + + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdBinaryArith(CallInfo& callInfo, JSNative native, + MSimdBinaryArith::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + auto* ins = MSimdBinaryArith::AddLegalized(alloc(), current, lhs, rhs, op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdBinaryBitwise(CallInfo& callInfo, JSNative native, + MSimdBinaryBitwise::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + auto* ins = MSimdBinaryBitwise::New(alloc(), lhs, rhs, op); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a binary SIMD operation where both arguments are SIMD types. +IonBuilder::InliningResult +IonBuilder::inlineSimdBinarySaturating(CallInfo& callInfo, JSNative native, + MSimdBinarySaturating::Operation op, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + + MSimdBinarySaturating* ins = + MSimdBinarySaturating::New(alloc(), lhs, rhs, op, GetSimdSign(type)); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a SIMD shiftByScalar operation. +IonBuilder::InliningResult +IonBuilder::inlineSimdShift(CallInfo& callInfo, JSNative native, MSimdShift::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* vec = unboxSimd(callInfo.getArg(0), type); + + MInstruction* ins = MSimdShift::AddLegalized(alloc(), current, vec, callInfo.getArg(1), op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdComp(CallInfo& callInfo, JSNative native, MSimdBinaryComp::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* lhs = unboxSimd(callInfo.getArg(0), type); + MDefinition* rhs = unboxSimd(callInfo.getArg(1), type); + MInstruction* ins = + MSimdBinaryComp::AddLegalized(alloc(), current, lhs, rhs, op, GetSimdSign(type)); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdUnary(CallInfo& callInfo, JSNative native, MSimdUnaryArith::Operation op, + SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* arg = unboxSimd(callInfo.getArg(0), type); + + MSimdUnaryArith* ins = MSimdUnaryArith::New(alloc(), arg, op); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdSplat(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MIRType mirType = SimdTypeToMIRType(type); + MDefinition* arg = callInfo.getArg(0); + + // Convert to 0 / -1 before splatting a boolean lane. + if (SimdTypeToLaneType(mirType) == MIRType::Boolean) + arg = convertToBooleanSimdLane(arg); + + MSimdSplat* ins = MSimdSplat::New(alloc(), arg, mirType); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdExtractLane(CallInfo& callInfo, JSNative native, SimdType type) +{ + // extractLane() returns a scalar, so don't use canInlineSimd() which looks + // for a template object. + if (callInfo.argc() != 2 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + // Lane index. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant() || arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + unsigned lane = arg->toConstant()->toInt32(); + if (lane >= GetSimdLanes(type)) + return InliningStatus_NotInlined; + + // Original vector. + MDefinition* orig = unboxSimd(callInfo.getArg(0), type); + MIRType vecType = orig->type(); + MIRType laneType = SimdTypeToLaneType(vecType); + SimdSign sign = GetSimdSign(type); + + // An Uint32 lane can't be represented in MIRType::Int32. Get it as a double. + if (type == SimdType::Uint32x4) + laneType = MIRType::Double; + + MSimdExtractElement* ins = + MSimdExtractElement::New(alloc(), orig, laneType, lane, sign); + current->add(ins); + current->push(ins); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdReplaceLane(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + // Lane index. + MDefinition* arg = callInfo.getArg(1); + if (!arg->isConstant() || arg->type() != MIRType::Int32) + return InliningStatus_NotInlined; + + unsigned lane = arg->toConstant()->toInt32(); + if (lane >= GetSimdLanes(type)) + return InliningStatus_NotInlined; + + // Original vector. + MDefinition* orig = unboxSimd(callInfo.getArg(0), type); + MIRType vecType = orig->type(); + + // Convert to 0 / -1 before inserting a boolean lane. + MDefinition* value = callInfo.getArg(2); + if (SimdTypeToLaneType(vecType) == MIRType::Boolean) + value = convertToBooleanSimdLane(value); + + MSimdInsertElement* ins = MSimdInsertElement::New(alloc(), orig, value, lane); + return boxSimd(callInfo, ins, templateObj); +} + +// Inline a SIMD conversion or bitcast. When isCast==false, one of the types +// must be floating point and the other integer. In this case, sign indicates if +// the integer lanes should be treated as signed or unsigned integers. +IonBuilder::InliningResult +IonBuilder::inlineSimdConvert(CallInfo& callInfo, JSNative native, bool isCast, SimdType fromType, + SimdType toType) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 1, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* arg = unboxSimd(callInfo.getArg(0), fromType); + MIRType mirType = SimdTypeToMIRType(toType); + + MInstruction* ins; + if (isCast) { + // Signed/Unsigned doesn't matter for bitcasts. + ins = MSimdReinterpretCast::New(alloc(), arg, mirType); + } else { + // Exactly one of fromType, toType must be an integer type. + SimdSign sign = GetSimdSign(fromType); + if (sign == SimdSign::NotApplicable) + sign = GetSimdSign(toType); + + // Possibly expand into multiple instructions. + ins = MSimdConvert::AddLegalized(alloc(), current, arg, mirType, sign); + } + + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdSelect(CallInfo& callInfo, JSNative native, SimdType type) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + MDefinition* mask = unboxSimd(callInfo.getArg(0), GetBooleanSimdType(type)); + MDefinition* tval = unboxSimd(callInfo.getArg(1), type); + MDefinition* fval = unboxSimd(callInfo.getArg(2), type); + + MSimdSelect* ins = MSimdSelect::New(alloc(), mask, tval, fval); + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdShuffle(CallInfo& callInfo, JSNative native, SimdType type, + unsigned numVectors) +{ + unsigned numLanes = GetSimdLanes(type); + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, numVectors + numLanes, &templateObj)) + return InliningStatus_NotInlined; + + MIRType mirType = SimdTypeToMIRType(type); + + MSimdGeneralShuffle* ins = MSimdGeneralShuffle::New(alloc(), numVectors, numLanes, mirType); + + if (!ins->init(alloc())) + return abort(AbortReason::Alloc); + + for (unsigned i = 0; i < numVectors; i++) + ins->setVector(i, unboxSimd(callInfo.getArg(i), type)); + for (size_t i = 0; i < numLanes; i++) + ins->setLane(i, callInfo.getArg(numVectors + i)); + + return boxSimd(callInfo, ins, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdAnyAllTrue(CallInfo& callInfo, bool IsAllTrue, JSNative native, + SimdType type) +{ + // anyTrue() / allTrue() return a scalar, so don't use canInlineSimd() which looks + // for a template object. + if (callInfo.argc() != 1 || callInfo.constructing()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineNativeBadForm); + return InliningStatus_NotInlined; + } + + MDefinition* arg = unboxSimd(callInfo.getArg(0), type); + + MUnaryInstruction* ins; + if (IsAllTrue) + ins = MSimdAllTrue::New(alloc(), arg, MIRType::Boolean); + else + ins = MSimdAnyTrue::New(alloc(), arg, MIRType::Boolean); + + current->add(ins); + current->push(ins); + callInfo.setImplicitlyUsedUnchecked(); + return InliningStatus_Inlined; +} + +// Get the typed array element type corresponding to the lanes in a SIMD vector type. +// This only applies to SIMD types that can be loaded and stored to a typed array. +static Scalar::Type +SimdTypeToArrayElementType(SimdType type) +{ + switch (type) { + case SimdType::Float32x4: return Scalar::Float32x4; + case SimdType::Int8x16: + case SimdType::Uint8x16: return Scalar::Int8x16; + case SimdType::Int16x8: + case SimdType::Uint16x8: return Scalar::Int16x8; + case SimdType::Int32x4: + case SimdType::Uint32x4: return Scalar::Int32x4; + default: MOZ_CRASH("unexpected simd type"); + } +} + +bool +IonBuilder::prepareForSimdLoadStore(CallInfo& callInfo, Scalar::Type simdType, + MInstruction** elements, MDefinition** index, + Scalar::Type* arrayType) +{ + MDefinition* array = callInfo.getArg(0); + *index = callInfo.getArg(1); + + if (!ElementAccessIsTypedArray(constraints(), array, *index, arrayType)) + return false; + + MInstruction* indexAsInt32 = MToNumberInt32::New(alloc(), *index); + current->add(indexAsInt32); + *index = indexAsInt32; + + MDefinition* indexLoadEnd = *index; + + MOZ_ASSERT(Scalar::byteSize(simdType) % Scalar::byteSize(*arrayType) == 0); + int32_t byteLoadSize = Scalar::byteSize(simdType) / Scalar::byteSize(*arrayType); + if (byteLoadSize > 1) { + // Add the number of supplementary needed slots. Overflows are fine + // because the bounds check code uses an unsigned comparison. + MAdd* addedIndex = MAdd::New(alloc(), *index, constant(Int32Value(byteLoadSize - 1))); + addedIndex->setInt32Specialization(); + current->add(addedIndex); + indexLoadEnd = addedIndex; + } + + MInstruction* length; + addTypedArrayLengthAndData(array, SkipBoundsCheck, index, &length, elements); + + // If the index+size addition overflows, then indexLoadEnd might be + // in bounds while the actual index isn't, so we need two bounds checks + // here. + if (byteLoadSize > 1) { + indexLoadEnd = addBoundsCheck(indexLoadEnd, length); + auto* sub = MSub::New(alloc(), indexLoadEnd, constant(Int32Value(byteLoadSize - 1))); + sub->setInt32Specialization(); + current->add(sub); + *index = sub; + } + + *index = addBoundsCheck(*index, length); + + return true; +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdLoad(CallInfo& callInfo, JSNative native, SimdType type, unsigned numElems) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 2, &templateObj)) + return InliningStatus_NotInlined; + + Scalar::Type elemType = SimdTypeToArrayElementType(type); + + MDefinition* index = nullptr; + MInstruction* elements = nullptr; + Scalar::Type arrayType; + if (!prepareForSimdLoadStore(callInfo, elemType, &elements, &index, &arrayType)) + return InliningStatus_NotInlined; + + MLoadUnboxedScalar* load = MLoadUnboxedScalar::New(alloc(), elements, index, arrayType); + load->setResultType(SimdTypeToMIRType(type)); + load->setSimdRead(elemType, numElems); + + return boxSimd(callInfo, load, templateObj); +} + +IonBuilder::InliningResult +IonBuilder::inlineSimdStore(CallInfo& callInfo, JSNative native, SimdType type, unsigned numElems) +{ + InlineTypedObject* templateObj = nullptr; + if (!canInlineSimd(callInfo, native, 3, &templateObj)) + return InliningStatus_NotInlined; + + Scalar::Type elemType = SimdTypeToArrayElementType(type); + + MDefinition* index = nullptr; + MInstruction* elements = nullptr; + Scalar::Type arrayType; + if (!prepareForSimdLoadStore(callInfo, elemType, &elements, &index, &arrayType)) + return InliningStatus_NotInlined; + + MDefinition* valueToWrite = unboxSimd(callInfo.getArg(2), type); + MStoreUnboxedScalar* store = MStoreUnboxedScalar::New(alloc(), elements, index, + valueToWrite, arrayType, + MStoreUnboxedScalar::TruncateInput); + store->setSimdWrite(elemType, numElems); + + current->add(store); + // Produce the original boxed value as our return value. + // This is unlikely to be used, so don't bother reboxing valueToWrite. + current->push(callInfo.getArg(2)); + + callInfo.setImplicitlyUsedUnchecked(); + + MOZ_TRY(resumeAfter(store)); + return InliningStatus_Inlined; +} + +// Note that SIMD.cpp provides its own JSJitInfo objects for SIMD.foo.* functions. +// The Simd* objects defined here represent SIMD.foo() constructor calls. +// They are encoded with .nativeOp = 0. That is the sub-opcode within the SIMD type. +static_assert(uint16_t(SimdOperation::Constructor) == 0, "Constructor opcode must be 0"); + #define ADD_NATIVE(native) const JSJitInfo JitInfo_##native { \ { nullptr }, { uint16_t(InlinableNative::native) }, { 0 }, JSJitInfo::InlinableNative }; INLINABLE_NATIVE_LIST(ADD_NATIVE) diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 542f02c9a696..4e9d3be7d447 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -17,6 +17,7 @@ #include "jslibmath.h" #include "builtin/RegExp.h" +#include "builtin/SIMD.h" #include "builtin/String.h" #include "jit/AtomicOperations.h" #include "jit/BaselineInspector.h" @@ -1311,7 +1312,531 @@ MWasmFloatConstant::congruentTo(const MDefinition* ins) const u.bits_ == ins->toWasmFloatConstant()->u.bits_; } +MDefinition* +MSimdValueX4::foldsTo(TempAllocator& alloc) +{ +#ifdef DEBUG + MIRType laneType = SimdTypeToLaneArgumentType(type()); +#endif + bool allConstants = true; + bool allSame = true; + + for (size_t i = 0; i < 4; ++i) { + MDefinition* op = getOperand(i); + MOZ_ASSERT(op->type() == laneType); + if (!op->isConstant()) + allConstants = false; + if (i > 0 && op != getOperand(i - 1)) + allSame = false; + } + + if (!allConstants && !allSame) + return this; + + if (allConstants) { + SimdConstant cst; + switch (type()) { + case MIRType::Bool32x4: { + int32_t a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::CreateX4(a); + break; + } + case MIRType::Int32x4: { + int32_t a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->toInt32(); + cst = SimdConstant::CreateX4(a); + break; + } + case MIRType::Float32x4: { + float a[4]; + for (size_t i = 0; i < 4; ++i) + a[i] = getOperand(i)->toConstant()->numberToDouble(); + cst = SimdConstant::CreateX4(a); + break; + } + default: MOZ_CRASH("unexpected type in MSimdValueX4::foldsTo"); + } + + return MSimdConstant::New(alloc, cst, type()); + } + + MOZ_ASSERT(allSame); + return MSimdSplat::New(alloc, getOperand(0), type()); +} + +MDefinition* +MSimdSplat::foldsTo(TempAllocator& alloc) +{ +#ifdef DEBUG + MIRType laneType = SimdTypeToLaneArgumentType(type()); +#endif + MDefinition* op = getOperand(0); + if (!op->isConstant()) + return this; + MOZ_ASSERT(op->type() == laneType); + + SimdConstant cst; + switch (type()) { + case MIRType::Bool8x16: { + int8_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX16(v); + break; + } + case MIRType::Bool16x8: { + int16_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX8(v); + break; + } + case MIRType::Bool32x4: { + int32_t v = op->toConstant()->valueToBooleanInfallible() ? -1 : 0; + cst = SimdConstant::SplatX4(v); + break; + } + case MIRType::Int8x16: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX16(v); + break; + } + case MIRType::Int16x8: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX8(v); + break; + } + case MIRType::Int32x4: { + int32_t v = op->toConstant()->toInt32(); + cst = SimdConstant::SplatX4(v); + break; + } + case MIRType::Float32x4: { + float v = op->toConstant()->numberToDouble(); + cst = SimdConstant::SplatX4(v); + break; + } + default: MOZ_CRASH("unexpected type in MSimdSplat::foldsTo"); + } + + return MSimdConstant::New(alloc, cst, type()); +} + +MDefinition* +MSimdUnbox::foldsTo(TempAllocator& alloc) +{ + MDefinition* in = input(); + + if (in->isSimdBox()) { + MSimdBox* box = in->toSimdBox(); + // If the operand is a MSimdBox, then we just reuse the operand of the + // MSimdBox as long as the type corresponds to what we are supposed to + // unbox. + in = box->input(); + if (box->simdType() != simdType()) + return this; + MOZ_ASSERT(in->type() == type()); + return in; + } + + return this; +} + +MDefinition* +MSimdSwizzle::foldsTo(TempAllocator& alloc) +{ + if (lanesMatch(0, 1, 2, 3)) + return input(); + return this; +} + +MDefinition* +MSimdGeneralShuffle::foldsTo(TempAllocator& alloc) +{ + FixedList lanes; + if (!lanes.init(alloc, numLanes())) + return this; + + for (size_t i = 0; i < numLanes(); i++) { + if (!lane(i)->isConstant() || lane(i)->type() != MIRType::Int32) + return this; + int32_t temp = lane(i)->toConstant()->toInt32(); + if (temp < 0 || unsigned(temp) >= numLanes() * numVectors()) + return this; + lanes[i] = uint8_t(temp); + } + + if (numVectors() == 1) + return MSimdSwizzle::New(alloc, vector(0), lanes.data()); + + MOZ_ASSERT(numVectors() == 2); + return MSimdShuffle::New(alloc, vector(0), vector(1), lanes.data()); +} + +MInstruction* +MSimdConvert::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* obj, + MIRType toType, SimdSign sign, wasm::BytecodeOffset bytecodeOffset) +{ + MIRType fromType = obj->type(); + + if (SupportsUint32x4FloatConversions || sign != SimdSign::Unsigned) { + MInstruction* ins = New(alloc, obj, toType, sign, bytecodeOffset); + addTo->add(ins); + return ins; + } + + // This architecture can't do Uint32x4 <-> Float32x4 conversions (Hi SSE!) + MOZ_ASSERT(sign == SimdSign::Unsigned); + if (fromType == MIRType::Int32x4 && toType == MIRType::Float32x4) { + // Converting Uint32x4 -> Float32x4. This algorithm is from LLVM. + // + // Split the input number into high and low parts: + // + // uint32_t hi = x >> 16; + // uint32_t lo = x & 0xffff; + // + // Insert these parts as the low mantissa bits in a float32 number with + // the corresponding exponent: + // + // float fhi = (bits-as-float)(hi | 0x53000000); // 0x1.0p39f + hi*2^16 + // float flo = (bits-as-float)(lo | 0x4b000000); // 0x1.0p23f + lo + // + // Subtract the bias from the hi part: + // + // fhi -= (0x1.0p39 + 0x1.0p23) // hi*2^16 - 0x1.0p23 + // + // And finally combine: + // + // result = flo + fhi // lo + hi*2^16. + + // Compute hi = obj >> 16 (lane-wise unsigned shift). + MInstruction* c16 = MConstant::New(alloc, Int32Value(16)); + addTo->add(c16); + MInstruction* hi = MSimdShift::AddLegalized(alloc, addTo, obj, c16, MSimdShift::ursh); + + // Compute lo = obj & 0xffff (lane-wise). + MInstruction* m16 = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0xffff), MIRType::Int32x4); + addTo->add(m16); + MInstruction* lo = MSimdBinaryBitwise::New(alloc, obj, m16, MSimdBinaryBitwise::and_); + addTo->add(lo); + + // Mix in the exponents. + MInstruction* exphi = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0x53000000), MIRType::Int32x4); + addTo->add(exphi); + MInstruction* mhi = MSimdBinaryBitwise::New(alloc, hi, exphi, MSimdBinaryBitwise::or_); + addTo->add(mhi); + MInstruction* explo = + MSimdConstant::New(alloc, SimdConstant::SplatX4(0x4b000000), MIRType::Int32x4); + addTo->add(explo); + MInstruction* mlo = MSimdBinaryBitwise::New(alloc, lo, explo, MSimdBinaryBitwise::or_); + addTo->add(mlo); + + // Bit-cast both to Float32x4. + MInstruction* fhi = MSimdReinterpretCast::New(alloc, mhi, MIRType::Float32x4); + addTo->add(fhi); + MInstruction* flo = MSimdReinterpretCast::New(alloc, mlo, MIRType::Float32x4); + addTo->add(flo); + + // Subtract out the bias: 0x1.0p39f + 0x1.0p23f. + // MSVC doesn't support the hexadecimal float syntax. + const float BiasValue = 549755813888.f + 8388608.f; + MInstruction* bias = + MSimdConstant::New(alloc, SimdConstant::SplatX4(BiasValue), MIRType::Float32x4); + addTo->add(bias); + MInstruction* fhi_debiased = + MSimdBinaryArith::AddLegalized(alloc, addTo, fhi, bias, MSimdBinaryArith::Op_sub); + + // Compute the final result. + return MSimdBinaryArith::AddLegalized(alloc, addTo, fhi_debiased, flo, + MSimdBinaryArith::Op_add); + } + + if (fromType == MIRType::Float32x4 && toType == MIRType::Int32x4) { + // The Float32x4 -> Uint32x4 conversion can throw if the input is out of + // range. This is handled by the LFloat32x4ToUint32x4 expansion. + MInstruction* ins = New(alloc, obj, toType, sign, bytecodeOffset); + addTo->add(ins); + return ins; + } + + MOZ_CRASH("Unhandled SIMD type conversion"); +} + +MInstruction* +MSimdBinaryComp::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op, SimdSign sign) +{ + MOZ_ASSERT(left->type() == right->type()); + MIRType opType = left->type(); + MOZ_ASSERT(IsSimdType(opType)); + bool IsEquality = op == equal || op == notEqual; + + // Check if this is an unsupported unsigned compare that needs to be biased. + // If so, put the bias vector in `bias`. + if (sign == SimdSign::Unsigned && !IsEquality) { + MInstruction* bias = nullptr; + + // This is an order comparison of Uint32x4 vectors which are not supported on this target. + // Simply offset |left| and |right| by INT_MIN, then do a signed comparison. + if (!SupportsUint32x4Compares && opType == MIRType::Int32x4) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX4(int32_t(0x80000000)), opType); + else if (!SupportsUint16x8Compares && opType == MIRType::Int16x8) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0x8000)), opType); + if (!SupportsUint8x16Compares && opType == MIRType::Int8x16) + bias = MSimdConstant::New(alloc, SimdConstant::SplatX16(int8_t(0x80)), opType); + + if (bias) { + addTo->add(bias); + + // Add the bias. + MInstruction* bleft = + MSimdBinaryArith::AddLegalized(alloc, addTo, left, bias, MSimdBinaryArith::Op_add); + MInstruction* bright = + MSimdBinaryArith::AddLegalized(alloc, addTo, right, bias, MSimdBinaryArith::Op_add); + + // Do the equivalent signed comparison. + MInstruction* result = + MSimdBinaryComp::New(alloc, bleft, bright, op, SimdSign::Signed); + addTo->add(result); + + return result; + } + } + + if (sign == SimdSign::Unsigned && + ((!SupportsUint32x4Compares && opType == MIRType::Int32x4) || + (!SupportsUint16x8Compares && opType == MIRType::Int16x8) || + (!SupportsUint8x16Compares && opType == MIRType::Int8x16))) { + // The sign doesn't matter for equality tests. Flip it to make the + // backend assertions happy. + MOZ_ASSERT(IsEquality); + sign = SimdSign::Signed; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdBinaryComp::New(alloc, left, right, op, sign); + addTo->add(result); + return result; +} + +MInstruction* +MSimdBinaryArith::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op) +{ + MOZ_ASSERT(left->type() == right->type()); + MIRType opType = left->type(); + MOZ_ASSERT(IsSimdType(opType)); + + // SSE does not have 8x16 multiply instructions. + if (opType == MIRType::Int8x16 && op == Op_mul) { + // Express the multiply in terms of Int16x8 multiplies by handling the + // even and odd lanes separately. + + MInstruction* wideL = MSimdReinterpretCast::New(alloc, left, MIRType::Int16x8); + addTo->add(wideL); + MInstruction* wideR = MSimdReinterpretCast::New(alloc, right, MIRType::Int16x8); + addTo->add(wideR); + + // wideL = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + // wideR = bbaa bbaa bbaa bbaa bbaa bbaa bbaa bbaa + + // Shift the odd lanes down to the low bits of the 16x8 vectors. + MInstruction* eight = MConstant::New(alloc, Int32Value(8)); + addTo->add(eight); + MInstruction* evenL = wideL; + MInstruction* evenR = wideR; + MInstruction* oddL = + MSimdShift::AddLegalized(alloc, addTo, wideL, eight, MSimdShift::ursh); + MInstruction* oddR = + MSimdShift::AddLegalized(alloc, addTo, wideR, eight, MSimdShift::ursh); + + // evenL = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + // evenR = bbaa bbaa bbaa bbaa bbaa bbaa bbaa bbaa + // oddL = 00yy 00yy 00yy 00yy 00yy 00yy 00yy 00yy + // oddR = 00bb 00bb 00bb 00bb 00bb 00bb 00bb 00bb + + // Now do two 16x8 multiplications. We can use the low bits of each. + MInstruction* even = MSimdBinaryArith::AddLegalized(alloc, addTo, evenL, evenR, Op_mul); + MInstruction* odd = MSimdBinaryArith::AddLegalized(alloc, addTo, oddL, oddR, Op_mul); + + // even = ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP ~~PP + // odd = ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ ~~QQ + + MInstruction* mask = + MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0x00ff)), MIRType::Int16x8); + addTo->add(mask); + even = MSimdBinaryBitwise::New(alloc, even, mask, MSimdBinaryBitwise::and_); + addTo->add(even); + odd = MSimdShift::AddLegalized(alloc, addTo, odd, eight, MSimdShift::lsh); + + // even = 00PP 00PP 00PP 00PP 00PP 00PP 00PP 00PP + // odd = QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 QQ00 + + // Combine: + MInstruction* result = MSimdBinaryBitwise::New(alloc, even, odd, MSimdBinaryBitwise::or_); + addTo->add(result); + result = MSimdReinterpretCast::New(alloc, result, opType); + addTo->add(result); + return result; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdBinaryArith::New(alloc, left, right, op); + addTo->add(result); + return result; +} + +MInstruction* +MSimdShift::AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op) +{ + MIRType opType = left->type(); + MOZ_ASSERT(IsIntegerSimdType(opType)); + + // SSE does not provide 8x16 shift instructions. + if (opType == MIRType::Int8x16) { + // Express the shift in terms of Int16x8 shifts by splitting into even + // and odd lanes, place 8-bit lanes into the high bits of Int16x8 + // vectors `even` and `odd`. Shift, mask, combine. + // + // wide = Int16x8.fromInt8x16Bits(left); + // shiftBy = right & 7 + // mask = Int16x8.splat(0xff00); + // + MInstruction* wide = MSimdReinterpretCast::New(alloc, left, MIRType::Int16x8); + addTo->add(wide); + + // wide = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + + MInstruction* shiftMask = MConstant::New(alloc, Int32Value(7)); + addTo->add(shiftMask); + MBinaryBitwiseInstruction* shiftBy = MBitAnd::New(alloc, right, shiftMask); + shiftBy->setInt32Specialization(); + addTo->add(shiftBy); + + // Move the even 8x16 lanes into the high bits of the 16x8 lanes. + MInstruction* eight = MConstant::New(alloc, Int32Value(8)); + addTo->add(eight); + MInstruction* even = MSimdShift::AddLegalized(alloc, addTo, wide, eight, lsh); + + // Leave the odd lanes in place. + MInstruction* odd = wide; + + // even = xx00 xx00 xx00 xx00 xx00 xx00 xx00 xx00 + // odd = yyxx yyxx yyxx yyxx yyxx yyxx yyxx yyxx + + MInstruction* mask = + MSimdConstant::New(alloc, SimdConstant::SplatX8(int16_t(0xff00)), MIRType::Int16x8); + addTo->add(mask); + + // Left-shift: Clear the low bits in `odd` before shifting. + if (op == lsh) { + odd = MSimdBinaryBitwise::New(alloc, odd, mask, MSimdBinaryBitwise::and_); + addTo->add(odd); + // odd = yy00 yy00 yy00 yy00 yy00 yy00 yy00 yy00 + } + + // Do the real shift twice: once for the even lanes, once for the odd + // lanes. This is a recursive call, but with a different type. + even = MSimdShift::AddLegalized(alloc, addTo, even, shiftBy, op); + odd = MSimdShift::AddLegalized(alloc, addTo, odd, shiftBy, op); + + // even = XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ XX~~ + // odd = YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ YY~~ + + // Right-shift: Clear the low bits in `odd` after shifting. + if (op != lsh) { + odd = MSimdBinaryBitwise::New(alloc, odd, mask, MSimdBinaryBitwise::and_); + addTo->add(odd); + // odd = YY00 YY00 YY00 YY00 YY00 YY00 YY00 YY00 + } + + // Move the even lanes back to their original place. + even = MSimdShift::AddLegalized(alloc, addTo, even, eight, ursh); + + // Now, `odd` contains the odd lanes properly shifted, and `even` + // contains the even lanes properly shifted: + // + // even = 00XX 00XX 00XX 00XX 00XX 00XX 00XX 00XX + // odd = YY00 YY00 YY00 YY00 YY00 YY00 YY00 YY00 + // + // Combine: + MInstruction* result = MSimdBinaryBitwise::New(alloc, even, odd, MSimdBinaryBitwise::or_); + addTo->add(result); + result = MSimdReinterpretCast::New(alloc, result, opType); + addTo->add(result); + return result; + } + + // This is a legal operation already. Just create the instruction requested. + MInstruction* result = MSimdShift::New(alloc, left, right, op); + addTo->add(result); + return result; +} + +template +static void +PrintOpcodeOperation(T* mir, GenericPrinter& out) +{ + mir->MDefinition::printOpcode(out); + out.printf(" (%s)", T::OperationName(mir->operation())); +} + #ifdef JS_JITSPEW +void +MSimdBinaryArith::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinarySaturating::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinaryBitwise::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdUnaryArith::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdBinaryComp::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} +void +MSimdShift::printOpcode(GenericPrinter& out) const +{ + PrintOpcodeOperation(this, out); +} + +void +MSimdInsertElement::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (lane %u)", lane()); +} + +void +MSimdBox::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (%s%s)", SimdTypeToString(simdType()), + initialHeap() == gc::TenuredHeap ? ", tenured" : ""); +} + +void +MSimdUnbox::printOpcode(GenericPrinter& out) const +{ + MDefinition::printOpcode(out); + out.printf(" (%s)", SimdTypeToString(simdType())); +} + void MControlInstruction::printOpcode(GenericPrinter& out) const { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 7d81f68b9a4b..403d2ae4f1c4 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -17,6 +17,7 @@ #include "mozilla/Attributes.h" #include "mozilla/MacroForEach.h" +#include "builtin/SIMDConstants.h" #include "jit/AtomicOp.h" #include "jit/BaselineIC.h" #include "jit/FixedList.h" @@ -94,6 +95,63 @@ MIRType MIRTypeFromValue(const js::Value& vp) return MIRTypeFromValueType(vp.extractNonDoubleType()); } +// If simdType is one of the SIMD types suported by Ion, set mirType to the +// corresponding MIRType, and return true. +// +// If simdType is not suported by Ion, return false. +static inline MOZ_MUST_USE +bool MaybeSimdTypeToMIRType(SimdType type, MIRType* mirType) +{ + switch (type) { + case SimdType::Uint32x4: + case SimdType::Int32x4: *mirType = MIRType::Int32x4; return true; + case SimdType::Uint16x8: + case SimdType::Int16x8: *mirType = MIRType::Int16x8; return true; + case SimdType::Uint8x16: + case SimdType::Int8x16: *mirType = MIRType::Int8x16; return true; + case SimdType::Float32x4: *mirType = MIRType::Float32x4; return true; + case SimdType::Bool32x4: *mirType = MIRType::Bool32x4; return true; + case SimdType::Bool16x8: *mirType = MIRType::Bool16x8; return true; + case SimdType::Bool8x16: *mirType = MIRType::Bool8x16; return true; + default: return false; + } +} + +// Convert a SimdType to the corresponding MIRType, or crash. +// +// Note that this is not an injective mapping: SimdType has signed and unsigned +// integer types that map to the same MIRType. +static inline +MIRType SimdTypeToMIRType(SimdType type) +{ + MIRType ret = MIRType::None; + MOZ_ALWAYS_TRUE(MaybeSimdTypeToMIRType(type, &ret)); + return ret; +} + +static inline +SimdType MIRTypeToSimdType(MIRType type) +{ + switch (type) { + case MIRType::Int32x4: return SimdType::Int32x4; + case MIRType::Int16x8: return SimdType::Int16x8; + case MIRType::Int8x16: return SimdType::Int8x16; + case MIRType::Float32x4: return SimdType::Float32x4; + case MIRType::Bool32x4: return SimdType::Bool32x4; + case MIRType::Bool16x8: return SimdType::Bool16x8; + case MIRType::Bool8x16: return SimdType::Bool8x16; + default: break; + } + MOZ_CRASH("unhandled MIRType"); +} + +// Get the boolean MIRType with the same shape as type. +static inline +MIRType MIRTypeToBooleanSimdType(MIRType type) +{ + return SimdTypeToMIRType(GetBooleanSimdType(MIRTypeToSimdType(type))); +} + #define MIR_FLAG_LIST(_) \ _(InWorklist) \ _(EmittedAtUses) \ @@ -1739,6 +1797,1054 @@ class MWasmFloatConstant : public MNullaryInstruction } }; +// Generic constructor of SIMD valuesX4. +class MSimdValueX4 + : public MQuaternaryInstruction, + public MixPolicy, SimdScalarPolicy<1>, + SimdScalarPolicy<2>, SimdScalarPolicy<3> >::Data +{ + protected: + MSimdValueX4(MIRType type, MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w) + : MQuaternaryInstruction(classOpcode, x, y, z, w) + { + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT(SimdTypeToLength(type) == 4); + + setMovable(); + setResultType(type); + } + + public: + INSTRUCTION_HEADER(SimdValueX4) + TRIVIAL_NEW_WRAPPERS + + bool canConsumeFloat32(MUse* use) const override { + return SimdTypeToLaneType(type()) == MIRType::Float32; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + + MDefinition* foldsTo(TempAllocator& alloc) override; + + ALLOW_CLONE(MSimdValueX4) +}; + +// Generic constructor of SIMD values with identical lanes. +class MSimdSplat + : public MUnaryInstruction, + public SimdScalarPolicy<0>::Data +{ + protected: + MSimdSplat(MDefinition* v, MIRType type) + : MUnaryInstruction(classOpcode, v) + { + MOZ_ASSERT(IsSimdType(type)); + setMovable(); + setResultType(type); + } + + public: + INSTRUCTION_HEADER(SimdSplat) + TRIVIAL_NEW_WRAPPERS + + bool canConsumeFloat32(MUse* use) const override { + return SimdTypeToLaneType(type()) == MIRType::Float32; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + + MDefinition* foldsTo(TempAllocator& alloc) override; + + ALLOW_CLONE(MSimdSplat) +}; + +// A constant SIMD value. +class MSimdConstant + : public MNullaryInstruction +{ + SimdConstant value_; + + protected: + MSimdConstant(const SimdConstant& v, MIRType type) + : MNullaryInstruction(classOpcode), + value_(v) + { + MOZ_ASSERT(IsSimdType(type)); + setMovable(); + setResultType(type); + } + + public: + INSTRUCTION_HEADER(SimdConstant) + TRIVIAL_NEW_WRAPPERS + + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isSimdConstant()) + return false; + // Bool32x4 and Int32x4 share the same underlying SimdConstant representation. + if (type() != ins->type()) + return false; + return value() == ins->toSimdConstant()->value(); + } + + const SimdConstant& value() const { + return value_; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + ALLOW_CLONE(MSimdConstant) +}; + +// Converts all lanes of a given vector into the type of another vector +class MSimdConvert + : public MUnaryInstruction, + public SimdPolicy<0>::Data +{ + // When either fromType or toType is an integer vector, should it be treated + // as signed or unsigned. Note that we don't support int-int conversions - + // use MSimdReinterpretCast for that. + SimdSign sign_; + wasm::BytecodeOffset bytecodeOffset_; + + MSimdConvert(MDefinition* obj, MIRType toType, SimdSign sign, + wasm::BytecodeOffset bytecodeOffset) + : MUnaryInstruction(classOpcode, obj), sign_(sign), bytecodeOffset_(bytecodeOffset) + { + MIRType fromType = obj->type(); + MOZ_ASSERT(IsSimdType(fromType)); + MOZ_ASSERT(IsSimdType(toType)); + // All conversions are int <-> float, so signedness is required. + MOZ_ASSERT(sign != SimdSign::NotApplicable); + + setResultType(toType); + specialization_ = fromType; // expects fromType as input + + setMovable(); + if (IsFloatingPointSimdType(fromType) && IsIntegerSimdType(toType)) { + // Does the extra range check => do not remove + setGuard(); + } + } + + static MSimdConvert* New(TempAllocator& alloc, MDefinition* obj, MIRType toType, SimdSign sign, + wasm::BytecodeOffset bytecodeOffset) + { + return new (alloc) MSimdConvert(obj, toType, sign, bytecodeOffset); + } + + public: + INSTRUCTION_HEADER(SimdConvert) + + // Create a MSimdConvert instruction and add it to the basic block. + // Possibly create and add an equivalent sequence of instructions instead if + // the current target doesn't support the requested conversion directly. + // Return the inserted MInstruction that computes the converted value. + static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* obj, + MIRType toType, SimdSign sign, + wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()); + + SimdSign signedness() const { + return sign_; + } + wasm::BytecodeOffset bytecodeOffset() const { + return bytecodeOffset_; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + if (!congruentIfOperandsEqual(ins)) + return false; + const MSimdConvert* other = ins->toSimdConvert(); + return sign_ == other->sign_; + } + ALLOW_CLONE(MSimdConvert) +}; + +// Casts bits of a vector input to another SIMD type (doesn't generate code). +class MSimdReinterpretCast + : public MUnaryInstruction, + public SimdPolicy<0>::Data +{ + MSimdReinterpretCast(MDefinition* obj, MIRType toType) + : MUnaryInstruction(classOpcode, obj) + { + MIRType fromType = obj->type(); + MOZ_ASSERT(IsSimdType(fromType)); + MOZ_ASSERT(IsSimdType(toType)); + setMovable(); + setResultType(toType); + specialization_ = fromType; // expects fromType as input + } + + public: + INSTRUCTION_HEADER(SimdReinterpretCast) + TRIVIAL_NEW_WRAPPERS + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + ALLOW_CLONE(MSimdReinterpretCast) +}; + +// Extracts a lane element from a given vector type, given by its lane symbol. +// +// For integer SIMD types, a SimdSign must be provided so the lane value can be +// converted to a scalar correctly. +class MSimdExtractElement + : public MUnaryInstruction, + public SimdPolicy<0>::Data +{ + protected: + unsigned lane_; + SimdSign sign_; + + MSimdExtractElement(MDefinition* obj, MIRType laneType, unsigned lane, SimdSign sign) + : MUnaryInstruction(classOpcode, obj), lane_(lane), sign_(sign) + { + MIRType vecType = obj->type(); + MOZ_ASSERT(IsSimdType(vecType)); + MOZ_ASSERT(lane < SimdTypeToLength(vecType)); + MOZ_ASSERT(!IsSimdType(laneType)); + MOZ_ASSERT((sign != SimdSign::NotApplicable) == IsIntegerSimdType(vecType), + "Signedness must be specified for integer SIMD extractLanes"); + // The resulting type should match the lane type. + // Allow extracting boolean lanes directly into an Int32 (for wasm). + // Allow extracting Uint32 lanes into a double. + // + // We also allow extracting Uint32 lanes into a MIRType::Int32. This is + // equivalent to extracting the Uint32 lane to a double and then + // applying MTruncateToInt32, but it bypasses the conversion to/from + // double. + MOZ_ASSERT(SimdTypeToLaneType(vecType) == laneType || + (IsBooleanSimdType(vecType) && laneType == MIRType::Int32) || + (vecType == MIRType::Int32x4 && laneType == MIRType::Double && + sign == SimdSign::Unsigned)); + + setMovable(); + specialization_ = vecType; + setResultType(laneType); + } + + public: + INSTRUCTION_HEADER(SimdExtractElement) + TRIVIAL_NEW_WRAPPERS + + unsigned lane() const { + return lane_; + } + + SimdSign signedness() const { + return sign_; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isSimdExtractElement()) + return false; + const MSimdExtractElement* other = ins->toSimdExtractElement(); + if (other->lane_ != lane_ || other->sign_ != sign_) + return false; + return congruentIfOperandsEqual(other); + } + ALLOW_CLONE(MSimdExtractElement) +}; + +// Replaces the datum in the given lane by a scalar value of the same type. +class MSimdInsertElement + : public MBinaryInstruction, + public MixPolicy< SimdSameAsReturnedTypePolicy<0>, SimdScalarPolicy<1> >::Data +{ + private: + unsigned lane_; + + MSimdInsertElement(MDefinition* vec, MDefinition* val, unsigned lane) + : MBinaryInstruction(classOpcode, vec, val), lane_(lane) + { + MIRType type = vec->type(); + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT(lane < SimdTypeToLength(type)); + setMovable(); + setResultType(type); + } + + public: + INSTRUCTION_HEADER(SimdInsertElement) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, vector), (1, value)) + + unsigned lane() const { + return lane_; + } + + bool canConsumeFloat32(MUse* use) const override { + return use == getUseFor(1) && SimdTypeToLaneType(type()) == MIRType::Float32; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + bool congruentTo(const MDefinition* ins) const override { + return binaryCongruentTo(ins) && lane_ == ins->toSimdInsertElement()->lane(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdInsertElement) +}; + +// Returns true if all lanes are true. +class MSimdAllTrue + : public MUnaryInstruction, + public SimdPolicy<0>::Data +{ + protected: + explicit MSimdAllTrue(MDefinition* obj, MIRType result) + : MUnaryInstruction(classOpcode, obj) + { + MIRType simdType = obj->type(); + MOZ_ASSERT(IsBooleanSimdType(simdType)); + MOZ_ASSERT(result == MIRType::Boolean || result == MIRType::Int32); + setResultType(result); + specialization_ = simdType; + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdAllTrue) + TRIVIAL_NEW_WRAPPERS + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + ALLOW_CLONE(MSimdAllTrue) +}; + +// Returns true if any lane is true. +class MSimdAnyTrue + : public MUnaryInstruction, + public SimdPolicy<0>::Data +{ + protected: + explicit MSimdAnyTrue(MDefinition* obj, MIRType result) + : MUnaryInstruction(classOpcode, obj) + { + MIRType simdType = obj->type(); + MOZ_ASSERT(IsBooleanSimdType(simdType)); + MOZ_ASSERT(result == MIRType::Boolean || result == MIRType::Int32); + setResultType(result); + specialization_ = simdType; + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdAnyTrue) + TRIVIAL_NEW_WRAPPERS + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + + ALLOW_CLONE(MSimdAnyTrue) +}; + +// Base for the MSimdSwizzle and MSimdShuffle classes. +class MSimdShuffleBase +{ + protected: + // As of now, there are at most 16 lanes. For each lane, we need to know + // which input we choose and which of the lanes we choose. + mozilla::Array lane_; + uint32_t arity_; + + MSimdShuffleBase(const uint8_t lanes[], MIRType type) + { + arity_ = SimdTypeToLength(type); + for (unsigned i = 0; i < arity_; i++) + lane_[i] = lanes[i]; + } + + bool sameLanes(const MSimdShuffleBase* other) const { + return arity_ == other->arity_ && + memcmp(&lane_[0], &other->lane_[0], arity_) == 0; + } + + public: + unsigned numLanes() const { + return arity_; + } + + unsigned lane(unsigned i) const { + MOZ_ASSERT(i < arity_); + return lane_[i]; + } + + bool lanesMatch(uint32_t x, uint32_t y, uint32_t z, uint32_t w) const { + return arity_ == 4 && lane(0) == x && lane(1) == y && lane(2) == z && + lane(3) == w; + } +}; + +// Applies a swizzle operation to the input, putting the input lanes as +// indicated in the output register's lanes. This implements the SIMD.js +// "swizzle" function, that takes one vector and an array of lane indexes. +class MSimdSwizzle + : public MUnaryInstruction, + public MSimdShuffleBase, + public NoTypePolicy::Data +{ + protected: + MSimdSwizzle(MDefinition* obj, const uint8_t lanes[]) + : MUnaryInstruction(classOpcode, obj), MSimdShuffleBase(lanes, obj->type()) + { + for (unsigned i = 0; i < arity_; i++) + MOZ_ASSERT(lane(i) < arity_); + setResultType(obj->type()); + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdSwizzle) + TRIVIAL_NEW_WRAPPERS + + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isSimdSwizzle()) + return false; + const MSimdSwizzle* other = ins->toSimdSwizzle(); + return sameLanes(other) && congruentIfOperandsEqual(other); + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + MDefinition* foldsTo(TempAllocator& alloc) override; + + ALLOW_CLONE(MSimdSwizzle) +}; + +// A "general shuffle" is a swizzle or a shuffle with non-constant lane +// indices. This is the one that Ion inlines and it can be folded into a +// MSimdSwizzle/MSimdShuffle if lane indices are constant. Performance of +// general swizzle/shuffle does not really matter, as we expect to get +// constant indices most of the time. +class MSimdGeneralShuffle : + public MVariadicInstruction, + public SimdShufflePolicy::Data +{ + unsigned numVectors_; + unsigned numLanes_; + + protected: + MSimdGeneralShuffle(unsigned numVectors, unsigned numLanes, MIRType type) + : MVariadicInstruction(classOpcode), numVectors_(numVectors), numLanes_(numLanes) + { + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT(SimdTypeToLength(type) == numLanes_); + + setResultType(type); + specialization_ = type; + setGuard(); // throws if lane index is out of bounds + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdGeneralShuffle); + TRIVIAL_NEW_WRAPPERS + + MOZ_MUST_USE bool init(TempAllocator& alloc) { + return MVariadicInstruction::init(alloc, numVectors_ + numLanes_); + } + void setVector(unsigned i, MDefinition* vec) { + MOZ_ASSERT(i < numVectors_); + initOperand(i, vec); + } + void setLane(unsigned i, MDefinition* laneIndex) { + MOZ_ASSERT(i < numLanes_); + initOperand(numVectors_ + i, laneIndex); + } + + unsigned numVectors() const { + return numVectors_; + } + unsigned numLanes() const { + return numLanes_; + } + MDefinition* vector(unsigned i) const { + MOZ_ASSERT(i < numVectors_); + return getOperand(i); + } + MDefinition* lane(unsigned i) const { + MOZ_ASSERT(i < numLanes_); + return getOperand(numVectors_ + i); + } + + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isSimdGeneralShuffle()) + return false; + const MSimdGeneralShuffle* other = ins->toSimdGeneralShuffle(); + return numVectors_ == other->numVectors() && + numLanes_ == other->numLanes() && + congruentIfOperandsEqual(other); + } + + MDefinition* foldsTo(TempAllocator& alloc) override; + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } +}; + +// Applies a shuffle operation to the inputs. The lane indexes select a source +// lane from the concatenation of the two input vectors. +class MSimdShuffle + : public MBinaryInstruction, + public MSimdShuffleBase, + public NoTypePolicy::Data +{ + MSimdShuffle(MDefinition* lhs, MDefinition* rhs, const uint8_t lanes[]) + : MBinaryInstruction(classOpcode, lhs, rhs), MSimdShuffleBase(lanes, lhs->type()) + { + MOZ_ASSERT(IsSimdType(lhs->type())); + MOZ_ASSERT(IsSimdType(rhs->type())); + MOZ_ASSERT(lhs->type() == rhs->type()); + for (unsigned i = 0; i < arity_; i++) + MOZ_ASSERT(lane(i) < 2 * arity_); + setResultType(lhs->type()); + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdShuffle) + + static MInstruction* New(TempAllocator& alloc, MDefinition* lhs, MDefinition* rhs, + const uint8_t lanes[]) + { + unsigned arity = SimdTypeToLength(lhs->type()); + + // Swap operands so that new lanes come from LHS in majority. + // In the balanced case, swap operands if needs be, in order to be able + // to do only one vshufps on x86. + unsigned lanesFromLHS = 0; + for (unsigned i = 0; i < arity; i++) { + if (lanes[i] < arity) + lanesFromLHS++; + } + + if (lanesFromLHS < arity / 2 || + (arity == 4 && lanesFromLHS == 2 && lanes[0] >= 4 && lanes[1] >= 4)) { + mozilla::Array newLanes; + for (unsigned i = 0; i < arity; i++) + newLanes[i] = (lanes[i] + arity) % (2 * arity); + return New(alloc, rhs, lhs, &newLanes[0]); + } + + // If all lanes come from the same vector, just use swizzle instead. + if (lanesFromLHS == arity) + return MSimdSwizzle::New(alloc, lhs, lanes); + + return new(alloc) MSimdShuffle(lhs, rhs, lanes); + } + + bool congruentTo(const MDefinition* ins) const override { + if (!ins->isSimdShuffle()) + return false; + const MSimdShuffle* other = ins->toSimdShuffle(); + return sameLanes(other) && binaryCongruentTo(other); + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + ALLOW_CLONE(MSimdShuffle) +}; + +class MSimdUnaryArith + : public MUnaryInstruction, + public SimdSameAsReturnedTypePolicy<0>::Data +{ + public: + enum Operation { +#define OP_LIST_(OP) OP, + FOREACH_FLOAT_SIMD_UNOP(OP_LIST_) + neg, + not_ +#undef OP_LIST_ + }; + + static const char* OperationName(Operation op) { + switch (op) { + case abs: return "abs"; + case neg: return "neg"; + case not_: return "not"; + case reciprocalApproximation: return "reciprocalApproximation"; + case reciprocalSqrtApproximation: return "reciprocalSqrtApproximation"; + case sqrt: return "sqrt"; + } + MOZ_CRASH("unexpected operation"); + } + + private: + Operation operation_; + + MSimdUnaryArith(MDefinition* def, Operation op) + : MUnaryInstruction(classOpcode, def), operation_(op) + { + MIRType type = def->type(); + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT_IF(IsIntegerSimdType(type), op == neg || op == not_); + setResultType(type); + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdUnaryArith) + TRIVIAL_NEW_WRAPPERS + + Operation operation() const { return operation_; } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins) && ins->toSimdUnaryArith()->operation() == operation(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdUnaryArith); +}; + +// Compares each value of a SIMD vector to each corresponding lane's value of +// another SIMD vector, and returns a boolean vector containing the results of +// the comparison: all bits are set to 1 if the comparison is true, 0 otherwise. +// When comparing integer vectors, a SimdSign must be provided to request signed +// or unsigned comparison. +class MSimdBinaryComp + : public MBinaryInstruction, + public SimdAllPolicy::Data +{ + public: + enum Operation { +#define NAME_(x) x, + FOREACH_COMP_SIMD_OP(NAME_) +#undef NAME_ + }; + + static const char* OperationName(Operation op) { + switch (op) { +#define NAME_(x) case x: return #x; + FOREACH_COMP_SIMD_OP(NAME_) +#undef NAME_ + } + MOZ_CRASH("unexpected operation"); + } + + private: + Operation operation_; + SimdSign sign_; + + MSimdBinaryComp(MDefinition* left, MDefinition* right, Operation op, SimdSign sign) + : MBinaryInstruction(classOpcode, left, right), operation_(op), sign_(sign) + { + MOZ_ASSERT(left->type() == right->type()); + MIRType opType = left->type(); + MOZ_ASSERT(IsSimdType(opType)); + MOZ_ASSERT((sign != SimdSign::NotApplicable) == IsIntegerSimdType(opType), + "Signedness must be specified for integer SIMD compares"); + setResultType(MIRTypeToBooleanSimdType(opType)); + specialization_ = opType; + setMovable(); + if (op == equal || op == notEqual) + setCommutative(); + } + + static MSimdBinaryComp* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, + Operation op, SimdSign sign) + { + return new (alloc) MSimdBinaryComp(left, right, op, sign); + } + + public: + INSTRUCTION_HEADER(SimdBinaryComp) + + // Create a MSimdBinaryComp or an equivalent sequence of instructions + // supported by the current target. + // Add all instructions to the basic block |addTo|. + static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op, SimdSign sign); + + AliasSet getAliasSet() const override + { + return AliasSet::None(); + } + + Operation operation() const { return operation_; } + SimdSign signedness() const { return sign_; } + MIRType specialization() const { return specialization_; } + + // Swap the operands and reverse the comparison predicate. + void reverse() { + switch (operation()) { + case greaterThan: operation_ = lessThan; break; + case greaterThanOrEqual: operation_ = lessThanOrEqual; break; + case lessThan: operation_ = greaterThan; break; + case lessThanOrEqual: operation_ = greaterThanOrEqual; break; + case equal: + case notEqual: + break; + default: MOZ_CRASH("Unexpected compare operation"); + } + swapOperands(); + } + + bool congruentTo(const MDefinition* ins) const override { + if (!binaryCongruentTo(ins)) + return false; + const MSimdBinaryComp* other = ins->toSimdBinaryComp(); + return specialization_ == other->specialization() && + operation_ == other->operation() && + sign_ == other->signedness(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdBinaryComp) +}; + +class MSimdBinaryArith + : public MBinaryInstruction, + public MixPolicy, SimdSameAsReturnedTypePolicy<1> >::Data +{ + public: + enum Operation { +#define OP_LIST_(OP) Op_##OP, + FOREACH_NUMERIC_SIMD_BINOP(OP_LIST_) + FOREACH_FLOAT_SIMD_BINOP(OP_LIST_) +#undef OP_LIST_ + }; + + static const char* OperationName(Operation op) { + switch (op) { +#define OP_CASE_LIST_(OP) case Op_##OP: return #OP; + FOREACH_NUMERIC_SIMD_BINOP(OP_CASE_LIST_) + FOREACH_FLOAT_SIMD_BINOP(OP_CASE_LIST_) +#undef OP_CASE_LIST_ + } + MOZ_CRASH("unexpected operation"); + } + + private: + Operation operation_; + + MSimdBinaryArith(MDefinition* left, MDefinition* right, Operation op) + : MBinaryInstruction(classOpcode, left, right), operation_(op) + { + MOZ_ASSERT(left->type() == right->type()); + MIRType type = left->type(); + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT_IF(IsIntegerSimdType(type), op == Op_add || op == Op_sub || op == Op_mul); + setResultType(type); + setMovable(); + if (op == Op_add || op == Op_mul || op == Op_min || op == Op_max) + setCommutative(); + } + + static MSimdBinaryArith* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, + Operation op) + { + return new (alloc) MSimdBinaryArith(left, right, op); + } + + public: + INSTRUCTION_HEADER(SimdBinaryArith) + + // Create an MSimdBinaryArith instruction and add it to the basic block. Possibly + // create and add an equivalent sequence of instructions instead if the + // current target doesn't support the requested shift operation directly. + static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op); + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + Operation operation() const { return operation_; } + + bool congruentTo(const MDefinition* ins) const override { + if (!binaryCongruentTo(ins)) + return false; + return operation_ == ins->toSimdBinaryArith()->operation(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdBinaryArith) +}; + +class MSimdBinarySaturating + : public MBinaryInstruction, + public MixPolicy, SimdSameAsReturnedTypePolicy<1>>::Data +{ + public: + enum Operation + { + add, + sub, + }; + + static const char* OperationName(Operation op) + { + switch (op) { + case add: + return "add"; + case sub: + return "sub"; + } + MOZ_CRASH("unexpected operation"); + } + + private: + Operation operation_; + SimdSign sign_; + + MSimdBinarySaturating(MDefinition* left, MDefinition* right, Operation op, SimdSign sign) + : MBinaryInstruction(classOpcode, left, right) + , operation_(op) + , sign_(sign) + { + MOZ_ASSERT(left->type() == right->type()); + MIRType type = left->type(); + MOZ_ASSERT(type == MIRType::Int8x16 || type == MIRType::Int16x8); + setResultType(type); + setMovable(); + if (op == add) + setCommutative(); + } + + public: + INSTRUCTION_HEADER(SimdBinarySaturating) + TRIVIAL_NEW_WRAPPERS + + AliasSet getAliasSet() const override { return AliasSet::None(); } + + Operation operation() const { return operation_; } + SimdSign signedness() const { return sign_; } + + bool congruentTo(const MDefinition* ins) const override + { + if (!binaryCongruentTo(ins)) + return false; + return operation_ == ins->toSimdBinarySaturating()->operation() && + sign_ == ins->toSimdBinarySaturating()->signedness(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdBinarySaturating) +}; + +class MSimdBinaryBitwise + : public MBinaryInstruction, + public MixPolicy, SimdSameAsReturnedTypePolicy<1> >::Data +{ + public: + enum Operation { + and_, + or_, + xor_ + }; + + static const char* OperationName(Operation op) { + switch (op) { + case and_: return "and"; + case or_: return "or"; + case xor_: return "xor"; + } + MOZ_CRASH("unexpected operation"); + } + + private: + Operation operation_; + + MSimdBinaryBitwise(MDefinition* left, MDefinition* right, Operation op) + : MBinaryInstruction(classOpcode, left, right), operation_(op) + { + MOZ_ASSERT(left->type() == right->type()); + MIRType type = left->type(); + MOZ_ASSERT(IsSimdType(type)); + setResultType(type); + setMovable(); + setCommutative(); + } + + public: + INSTRUCTION_HEADER(SimdBinaryBitwise) + TRIVIAL_NEW_WRAPPERS + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + Operation operation() const { return operation_; } + + bool congruentTo(const MDefinition* ins) const override { + if (!binaryCongruentTo(ins)) + return false; + return operation_ == ins->toSimdBinaryBitwise()->operation(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + ALLOW_CLONE(MSimdBinaryBitwise) +}; + +class MSimdShift + : public MBinaryInstruction, + public MixPolicy, SimdScalarPolicy<1> >::Data +{ + public: + enum Operation { + lsh, + rsh, + ursh + }; + + private: + Operation operation_; + + MSimdShift(MDefinition* left, MDefinition* right, Operation op) + : MBinaryInstruction(classOpcode, left, right), operation_(op) + { + MIRType type = left->type(); + MOZ_ASSERT(IsIntegerSimdType(type)); + setResultType(type); + setMovable(); + } + + static MSimdShift* New(TempAllocator& alloc, MDefinition* left, MDefinition* right, + Operation op) + { + return new (alloc) MSimdShift(left, right, op); + } + + public: + INSTRUCTION_HEADER(SimdShift) + + // Create an MSimdShift instruction and add it to the basic block. Possibly + // create and add an equivalent sequence of instructions instead if the + // current target doesn't support the requested shift operation directly. + // Return the inserted MInstruction that computes the shifted value. + static MInstruction* AddLegalized(TempAllocator& alloc, MBasicBlock* addTo, MDefinition* left, + MDefinition* right, Operation op); + + // Get the relevant right shift operation given the signedness of a type. + static Operation rshForSign(SimdSign sign) { + return sign == SimdSign::Unsigned ? ursh : rsh; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + Operation operation() const { return operation_; } + + static const char* OperationName(Operation op) { + switch (op) { + case lsh: return "lsh"; + case rsh: return "rsh-arithmetic"; + case ursh: return "rsh-logical"; + } + MOZ_CRASH("unexpected operation"); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + bool congruentTo(const MDefinition* ins) const override { + if (!binaryCongruentTo(ins)) + return false; + return operation_ == ins->toSimdShift()->operation(); + } + + ALLOW_CLONE(MSimdShift) +}; + +class MSimdSelect + : public MTernaryInstruction, + public SimdSelectPolicy::Data +{ + MSimdSelect(MDefinition* mask, MDefinition* lhs, MDefinition* rhs) + : MTernaryInstruction(classOpcode, mask, lhs, rhs) + { + MOZ_ASSERT(IsBooleanSimdType(mask->type())); + MOZ_ASSERT(lhs->type() == lhs->type()); + MIRType type = lhs->type(); + MOZ_ASSERT(IsSimdType(type)); + setResultType(type); + specialization_ = type; + setMovable(); + } + + public: + INSTRUCTION_HEADER(SimdSelect) + TRIVIAL_NEW_WRAPPERS + NAMED_OPERANDS((0, mask)) + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + + bool congruentTo(const MDefinition* ins) const override { + return congruentIfOperandsEqual(ins); + } + + ALLOW_CLONE(MSimdSelect) +}; + // Deep clone a constant JSObject. class MCloneLiteral : public MUnaryInstruction, @@ -2634,6 +3740,123 @@ class MTypedObjectDescr } }; +// Generic way for constructing a SIMD object in IonMonkey, this instruction +// takes as argument a SIMD instruction and returns a new SIMD object which +// corresponds to the MIRType of its operand. +class MSimdBox + : public MUnaryInstruction, + public NoTypePolicy::Data +{ + protected: + CompilerGCPointer templateObject_; + SimdType simdType_; + gc::InitialHeap initialHeap_; + + MSimdBox(TempAllocator& alloc, + CompilerConstraintList* constraints, + MDefinition* op, + InlineTypedObject* templateObject, + SimdType simdType, + gc::InitialHeap initialHeap) + : MUnaryInstruction(classOpcode, op), + templateObject_(templateObject), + simdType_(simdType), + initialHeap_(initialHeap) + { + MOZ_ASSERT(IsSimdType(op->type())); + setMovable(); + setResultType(MIRType::Object); + if (constraints) + setResultTypeSet(MakeSingletonTypeSet(alloc, constraints, templateObject)); + } + + public: + INSTRUCTION_HEADER(SimdBox) + TRIVIAL_NEW_WRAPPERS_WITH_ALLOC + + InlineTypedObject* templateObject() const { + return templateObject_; + } + + SimdType simdType() const { + return simdType_; + } + + gc::InitialHeap initialHeap() const { + return initialHeap_; + } + + bool congruentTo(const MDefinition* ins) const override { + if (!congruentIfOperandsEqual(ins)) + return false; + const MSimdBox* box = ins->toSimdBox(); + if (box->simdType() != simdType()) + return false; + MOZ_ASSERT(box->templateObject() == templateObject()); + if (box->initialHeap() != initialHeap()) + return false; + return true; + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif + + MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override; + bool canRecoverOnBailout() const override { + return true; + } + + bool appendRoots(MRootList& roots) const override { + return roots.append(templateObject_); + } +}; + +class MSimdUnbox + : public MUnaryInstruction, + public SingleObjectPolicy::Data +{ + protected: + SimdType simdType_; + + MSimdUnbox(MDefinition* op, SimdType simdType) + : MUnaryInstruction(classOpcode, op), + simdType_(simdType) + { + MIRType type = SimdTypeToMIRType(simdType); + MOZ_ASSERT(IsSimdType(type)); + setGuard(); + setMovable(); + setResultType(type); + } + + public: + INSTRUCTION_HEADER(SimdUnbox) + TRIVIAL_NEW_WRAPPERS + ALLOW_CLONE(MSimdUnbox) + + SimdType simdType() const { return simdType_; } + + MDefinition* foldsTo(TempAllocator& alloc) override; + bool congruentTo(const MDefinition* ins) const override { + if (!congruentIfOperandsEqual(ins)) + return false; + return ins->toSimdUnbox()->simdType() == simdType(); + } + + AliasSet getAliasSet() const override { + return AliasSet::None(); + } + +#ifdef JS_JITSPEW + void printOpcode(GenericPrinter& out) const override; +#endif +}; + // Creates a new derived type object. At runtime, this is just a call // to `BinaryBlock::createDerived()`. That is, the MIR itself does not // compile to particularly optimized code. However, using a distinct @@ -9292,6 +10515,7 @@ class MLoadUnboxedScalar { Scalar::Type storageType_; Scalar::Type readType_; + unsigned numElems_; // used only for SIMD bool requiresBarrier_; int32_t offsetAdjustment_; bool canonicalizeDoubles_; @@ -9302,6 +10526,7 @@ class MLoadUnboxedScalar : MBinaryInstruction(classOpcode, elements, index), storageType_(storageType), readType_(storageType), + numElems_(1), requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier), offsetAdjustment_(offsetAdjustment), canonicalizeDoubles_(canonicalizeDoubles) @@ -9321,6 +10546,13 @@ class MLoadUnboxedScalar TRIVIAL_NEW_WRAPPERS NAMED_OPERANDS((0, elements), (1, index)) + void setSimdRead(Scalar::Type type, unsigned numElems) { + readType_ = type; + numElems_ = numElems; + } + unsigned numElems() const { + return numElems_; + } Scalar::Type readType() const { return readType_; } @@ -9362,6 +10594,8 @@ class MLoadUnboxedScalar return false; if (readType_ != other->readType_) return false; + if (numElems_ != other->numElems_) + return false; if (offsetAdjustment() != other->offsetAdjustment()) return false; if (canonicalizeDoubles() != other->canonicalizeDoubles()) @@ -9439,7 +10673,7 @@ class StoreUnboxedScalarBase explicit StoreUnboxedScalarBase(Scalar::Type writeType) : writeType_(writeType) { - MOZ_ASSERT(isIntegerWrite() || isFloatWrite()); + MOZ_ASSERT(isIntegerWrite() || isFloatWrite() || isSimdWrite()); } public: @@ -9465,6 +10699,9 @@ class StoreUnboxedScalarBase return writeType_ == Scalar::Float32 || writeType_ == Scalar::Float64; } + bool isSimdWrite() const { + return Scalar::isSimdType(writeType()); + } }; // Store an unboxed scalar value to a typed array or other object. @@ -9487,6 +10724,7 @@ class MStoreUnboxedScalar bool requiresBarrier_; int32_t offsetAdjustment_; + unsigned numElems_; // used only for SIMD MStoreUnboxedScalar(MDefinition* elements, MDefinition* index, MDefinition* value, Scalar::Type storageType, TruncateInputKind truncateInput, @@ -9497,7 +10735,8 @@ class MStoreUnboxedScalar storageType_(storageType), truncateInput_(truncateInput), requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier), - offsetAdjustment_(offsetAdjustment) + offsetAdjustment_(offsetAdjustment), + numElems_(1) { if (requiresBarrier_) setGuard(); // Not removable or movable @@ -9513,6 +10752,14 @@ class MStoreUnboxedScalar TRIVIAL_NEW_WRAPPERS NAMED_OPERANDS((0, elements), (1, index), (2, value)) + void setSimdWrite(Scalar::Type writeType, unsigned numElems) { + MOZ_ASSERT(Scalar::isSimdType(writeType)); + setWriteType(writeType); + numElems_ = numElems; + } + unsigned numElems() const { + return numElems_; + } Scalar::Type storageType() const { return storageType_; } @@ -9560,6 +10807,8 @@ class MStoreTypedArrayElementHole NAMED_OPERANDS((0, elements), (1, length), (2, index), (3, value)) Scalar::Type arrayType() const { + MOZ_ASSERT(!Scalar::isSimdType(writeType()), + "arrayType == writeType iff the write type isn't SIMD"); return writeType(); } AliasSet getAliasSet() const override { @@ -13334,6 +14583,7 @@ class MAsmJSMemoryAccess needsBoundsCheck_(true) { MOZ_ASSERT(accessType != Scalar::Uint8Clamped); + MOZ_ASSERT(!Scalar::isSimdType(accessType)); } uint32_t offset() const { return offset_; } @@ -13643,7 +14893,7 @@ class MWasmLoadGlobalVar isConstant_(isConstant), isIndirect_(isIndirect) { - MOZ_ASSERT(IsNumberType(type)); + MOZ_ASSERT(IsNumberType(type) || IsSimdType(type)); setResultType(type); setMovable(); } diff --git a/js/src/jit/MIRGenerator.h b/js/src/jit/MIRGenerator.h index 6276a52440c9..54e4fe8820b7 100644 --- a/js/src/jit/MIRGenerator.h +++ b/js/src/jit/MIRGenerator.h @@ -158,6 +158,10 @@ class MIRGenerator return needsOverrecursedCheck_; } + // Traverses the graph to find if there's any SIMD instruction. Costful but + // the value is cached, so don't worry about calling it several times. + bool usesSimd(); + bool modifiesFrameArguments() const { return modifiesFrameArguments_; } @@ -187,6 +191,8 @@ class MIRGenerator uint32_t wasmMaxStackArgBytes_; bool needsOverrecursedCheck_; bool needsStaticStackAlignment_; + bool usesSimd_; + bool cachedUsesSimd_; // Keep track of whether frame arguments are modified during execution. // RegAlloc needs to know this as spilling values back to their register diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 2da7bf28d137..faed7a2d4a88 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -32,6 +32,8 @@ MIRGenerator::MIRGenerator(CompileRealm* realm, const JitCompileOptions& options wasmMaxStackArgBytes_(0), needsOverrecursedCheck_(false), needsStaticStackAlignment_(false), + usesSimd_(false), + cachedUsesSimd_(false), modifiesFrameArguments_(false), instrumentedProfiling_(false), instrumentedProfilingIsCached_(false), @@ -42,6 +44,37 @@ MIRGenerator::MIRGenerator(CompileRealm* realm, const JitCompileOptions& options gs_(alloc) { } +bool +MIRGenerator::usesSimd() +{ + if (cachedUsesSimd_) + return usesSimd_; + + cachedUsesSimd_ = true; + for (ReversePostorderIterator block = graph_->rpoBegin(), + end = graph_->rpoEnd(); + block != end; + block++) + { + // It's fine to use MInstructionIterator here because we don't have to + // worry about Phis, since any reachable phi (or phi cycle) will have at + // least one instruction as an input. + for (MInstructionIterator inst = block->begin(); inst != block->end(); inst++) { + // Instructions that have SIMD inputs but not a SIMD type are fine + // to ignore, as their inputs are also reached at some point. By + // induction, at least one instruction with a SIMD type is reached + // at some point. + if (IsSimdType(inst->type())) { + MOZ_ASSERT(SupportsSimd); + usesSimd_ = true; + return true; + } + } + } + usesSimd_ = false; + return false; +} + mozilla::GenericErrorResult MIRGenerator::abort(AbortReason r) { diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index b27d85e1e288..bc7483f9d678 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -340,7 +340,8 @@ template void MacroAssembler::guardTypeSet(const TypedOrValueRegister& value, co template static void -StoreToTypedFloatArray(MacroAssembler& masm, int arrayType, const S& value, const T& dest) +StoreToTypedFloatArray(MacroAssembler& masm, int arrayType, const S& value, const T& dest, + unsigned numElems) { switch (arrayType) { case Scalar::Float32: @@ -349,6 +350,48 @@ StoreToTypedFloatArray(MacroAssembler& masm, int arrayType, const S& value, cons case Scalar::Float64: masm.storeDouble(value, dest); break; + case Scalar::Float32x4: + switch (numElems) { + case 1: + masm.storeFloat32(value, dest); + break; + case 2: + masm.storeDouble(value, dest); + break; + case 3: + masm.storeFloat32x3(value, dest); + break; + case 4: + masm.storeUnalignedSimd128Float(value, dest); + break; + default: MOZ_CRASH("unexpected number of elements in simd write"); + } + break; + case Scalar::Int32x4: + switch (numElems) { + case 1: + masm.storeInt32x1(value, dest); + break; + case 2: + masm.storeInt32x2(value, dest); + break; + case 3: + masm.storeInt32x3(value, dest); + break; + case 4: + masm.storeUnalignedSimd128Int(value, dest); + break; + default: MOZ_CRASH("unexpected number of elements in simd write"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(numElems == 16, "unexpected partial store"); + masm.storeUnalignedSimd128Int(value, dest); + break; + case Scalar::Int16x8: + MOZ_ASSERT(numElems == 8, "unexpected partial store"); + masm.storeUnalignedSimd128Int(value, dest); + break; default: MOZ_CRASH("Invalid typed array type"); } @@ -356,21 +399,21 @@ StoreToTypedFloatArray(MacroAssembler& masm, int arrayType, const S& value, cons void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, - const BaseIndex& dest) + const BaseIndex& dest, unsigned numElems) { - StoreToTypedFloatArray(*this, arrayType, value, dest); + StoreToTypedFloatArray(*this, arrayType, value, dest, numElems); } void MacroAssembler::storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, - const Address& dest) + const Address& dest, unsigned numElems) { - StoreToTypedFloatArray(*this, arrayType, value, dest); + StoreToTypedFloatArray(*this, arrayType, value, dest, numElems); } template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, AnyRegister dest, Register temp, - Label* fail, bool canonicalizeDoubles) + Label* fail, bool canonicalizeDoubles, unsigned numElems) { switch (arrayType) { case Scalar::Int8: @@ -411,17 +454,59 @@ MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, AnyRegi if (canonicalizeDoubles) canonicalizeDouble(dest.fpu()); break; + case Scalar::Int32x4: + switch (numElems) { + case 1: + loadInt32x1(src, dest.fpu()); + break; + case 2: + loadInt32x2(src, dest.fpu()); + break; + case 3: + loadInt32x3(src, dest.fpu()); + break; + case 4: + loadUnalignedSimd128Int(src, dest.fpu()); + break; + default: MOZ_CRASH("unexpected number of elements in SIMD load"); + } + break; + case Scalar::Float32x4: + switch (numElems) { + case 1: + loadFloat32(src, dest.fpu()); + break; + case 2: + loadDouble(src, dest.fpu()); + break; + case 3: + loadFloat32x3(src, dest.fpu()); + break; + case 4: + loadUnalignedSimd128Float(src, dest.fpu()); + break; + default: MOZ_CRASH("unexpected number of elements in SIMD load"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(numElems == 16, "unexpected partial load"); + loadUnalignedSimd128Int(src, dest.fpu()); + break; + case Scalar::Int16x8: + MOZ_ASSERT(numElems == 8, "unexpected partial load"); + loadUnalignedSimd128Int(src, dest.fpu()); + break; default: MOZ_CRASH("Invalid typed array type"); } } -template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const Address& src, - AnyRegister dest, Register temp, Label* fail, - bool canonicalizeDoubles); -template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, - AnyRegister dest, Register temp, Label* fail, - bool canonicalizeDoubles); +template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const Address& src, AnyRegister dest, + Register temp, Label* fail, bool canonicalizeDoubles, + unsigned numElems); +template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, AnyRegister dest, + Register temp, Label* fail, bool canonicalizeDoubles, + unsigned numElems); template void @@ -3310,6 +3395,41 @@ MacroAssembler::branchIfInlineTypedObject(Register obj, Register scratch, Label* branchPtr(Assembler::Equal, scratch, ImmPtr(&InlineTransparentTypedObject::class_), label); } +void +MacroAssembler::branchIfNotSimdObject(Register obj, Register scratch, SimdType simdType, + Label* label) +{ + loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch); + + // Guard that the object has the same representation as the one produced for + // SIMD value-type. + Address clasp(scratch, ObjectGroup::offsetOfClasp()); + static_assert(!SimdTypeDescr::Opaque, "SIMD objects are transparent"); + branchPtr(Assembler::NotEqual, clasp, ImmPtr(&InlineTransparentTypedObject::class_), + label); + + // obj->type()->typeDescr() + // The previous class pointer comparison implies that the addendumKind is + // Addendum_TypeDescr. + loadPtr(Address(scratch, ObjectGroup::offsetOfAddendum()), scratch); + + // Check for the /Kind/ reserved slot of the TypeDescr. This is an Int32 + // Value which is equivalent to the object class check. + static_assert(JS_DESCR_SLOT_KIND < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots"); + Address typeDescrKind(scratch, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_KIND)); + assertTestInt32(Assembler::Equal, typeDescrKind, + "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_KIND).isInt32())"); + branch32(Assembler::NotEqual, ToPayload(typeDescrKind), Imm32(js::type::Simd), label); + + // Check if the SimdTypeDescr /Type/ matches the specialization of this + // MSimdUnbox instruction. + static_assert(JS_DESCR_SLOT_TYPE < NativeObject::MAX_FIXED_SLOTS, "Load from fixed slots"); + Address typeDescrType(scratch, NativeObject::getFixedSlotOffset(JS_DESCR_SLOT_TYPE)); + assertTestInt32(Assembler::Equal, typeDescrType, + "MOZ_ASSERT(obj->type()->typeDescr()->getReservedSlot(JS_DESCR_SLOT_TYPE).isInt32())"); + branch32(Assembler::NotEqual, ToPayload(typeDescrType), Imm32(int32_t(simdType)), label); +} + void MacroAssembler::copyObjGroupNoPreBarrier(Register sourceObj, Register destObj, Register scratch) { diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index de1abb8d07a4..73db96d347a8 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1158,6 +1158,8 @@ class MacroAssembler : public MacroAssemblerSpecific void branchIfInlineTypedObject(Register obj, Register scratch, Label* label); + void branchIfNotSimdObject(Register obj, Register scratch, SimdType simdType, Label* label); + inline void branchTestClassIsProxy(bool proxy, Register clasp, Label* label); inline void branchTestObjectIsProxy(bool proxy, Register object, Register scratch, Label* label); @@ -1374,6 +1376,9 @@ class MacroAssembler : public MacroAssemblerSpecific inline void canonicalizeFloat(FloatRegister reg); inline void canonicalizeFloatIfDeterministic(FloatRegister reg); + inline void canonicalizeFloat32x4(FloatRegister reg, FloatRegister scratch) + DEFINED_ON(x86_shared); + public: // ======================================================================== // Memory access primitives. @@ -2148,7 +2153,7 @@ class MacroAssembler : public MacroAssemblerSpecific template void loadFromTypedArray(Scalar::Type arrayType, const T& src, AnyRegister dest, Register temp, Label* fail, - bool canonicalizeDoubles = true); + bool canonicalizeDoubles = true, unsigned numElems = 0); template void loadFromTypedArray(Scalar::Type arrayType, const T& src, const ValueOperand& dest, bool allowDouble, @@ -2175,8 +2180,10 @@ class MacroAssembler : public MacroAssemblerSpecific } } - void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest); - void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest); + void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest, + unsigned numElems = 0); + void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, + unsigned numElems = 0); void memoryBarrierBefore(const Synchronization& sync); void memoryBarrierAfter(const Synchronization& sync); diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index 1e3f2252825d..df67307eceaf 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1785,6 +1785,10 @@ GetTypedArrayRange(TempAllocator& alloc, Scalar::Type type) case Scalar::Int64: case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 211c9008a2c2..732fc9821a0d 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -10,6 +10,7 @@ #include "jsmath.h" #include "builtin/RegExp.h" +#include "builtin/SIMD.h" #include "builtin/String.h" #include "builtin/TypedObject.h" #include "gc/Heap.h" @@ -1661,6 +1662,79 @@ RNewCallObject::recover(JSContext* cx, SnapshotIterator& iter) const return true; } +bool +MSimdBox::writeRecoverData(CompactBufferWriter& writer) const +{ + MOZ_ASSERT(canRecoverOnBailout()); + writer.writeUnsigned(uint32_t(RInstruction::Recover_SimdBox)); + static_assert(unsigned(SimdType::Count) < 0x100, "assuming SimdType fits in 8 bits"); + writer.writeByte(uint8_t(simdType())); + return true; +} + +RSimdBox::RSimdBox(CompactBufferReader& reader) +{ + type_ = reader.readByte(); +} + +bool +RSimdBox::recover(JSContext* cx, SnapshotIterator& iter) const +{ + JSObject* resultObject = nullptr; + RValueAllocation a = iter.readAllocation(); + MOZ_ASSERT(iter.allocationReadable(a)); + MOZ_ASSERT_IF(a.mode() == RValueAllocation::ANY_FLOAT_REG, a.fpuReg().isSimd128()); + const FloatRegisters::RegisterContent* raw = iter.floatAllocationPointer(a); + switch (SimdType(type_)) { + case SimdType::Bool8x16: + resultObject = js::CreateSimd(cx, (const Bool8x16::Elem*) raw); + break; + case SimdType::Int8x16: + resultObject = js::CreateSimd(cx, (const Int8x16::Elem*) raw); + break; + case SimdType::Uint8x16: + resultObject = js::CreateSimd(cx, (const Uint8x16::Elem*) raw); + break; + case SimdType::Bool16x8: + resultObject = js::CreateSimd(cx, (const Bool16x8::Elem*) raw); + break; + case SimdType::Int16x8: + resultObject = js::CreateSimd(cx, (const Int16x8::Elem*) raw); + break; + case SimdType::Uint16x8: + resultObject = js::CreateSimd(cx, (const Uint16x8::Elem*) raw); + break; + case SimdType::Bool32x4: + resultObject = js::CreateSimd(cx, (const Bool32x4::Elem*) raw); + break; + case SimdType::Int32x4: + resultObject = js::CreateSimd(cx, (const Int32x4::Elem*) raw); + break; + case SimdType::Uint32x4: + resultObject = js::CreateSimd(cx, (const Uint32x4::Elem*) raw); + break; + case SimdType::Float32x4: + resultObject = js::CreateSimd(cx, (const Float32x4::Elem*) raw); + break; + case SimdType::Float64x2: + MOZ_CRASH("NYI, RSimdBox of Float64x2"); + break; + case SimdType::Bool64x2: + MOZ_CRASH("NYI, RSimdBox of Bool64x2"); + break; + case SimdType::Count: + MOZ_CRASH("RSimdBox of Count is unreachable"); + } + + if (!resultObject) + return false; + + RootedValue result(cx); + result.setObject(*resultObject); + iter.storeInstructionResult(result); + return true; +} + bool MObjectState::writeRecoverData(CompactBufferWriter& writer) const { diff --git a/js/src/jit/Recover.h b/js/src/jit/Recover.h index d23c1802b8d5..696472f9ac2d 100644 --- a/js/src/jit/Recover.h +++ b/js/src/jit/Recover.h @@ -112,6 +112,7 @@ namespace jit { _(CreateThisWithTemplate) \ _(Lambda) \ _(LambdaArrow) \ + _(SimdBox) \ _(ObjectState) \ _(ArrayState) \ _(SetArrayLength) \ @@ -689,6 +690,17 @@ class RNewCallObject final : public RInstruction MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const override; }; +class RSimdBox final : public RInstruction +{ + private: + uint8_t type_; + + public: + RINSTRUCTION_HEADER_NUM_OP_(SimdBox, 1) + + MOZ_MUST_USE bool recover(JSContext* cx, SnapshotIterator& iter) const override; +}; + class RObjectState final : public RInstruction { private: diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index f581e08207bd..927d88e68062 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -637,6 +637,45 @@ template bool NoFloatPolicyAfter<0>::adjustInputs(TempAllocator& alloc, MInstruc template bool NoFloatPolicyAfter<1>::adjustInputs(TempAllocator& alloc, MInstruction* def) const; template bool NoFloatPolicyAfter<2>::adjustInputs(TempAllocator& alloc, MInstruction* def) const; +template +bool +SimdScalarPolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + MIRType laneType = SimdTypeToLaneType(ins->type()); + + MDefinition* in = ins->getOperand(Op); + + // A vector with boolean lanes requires Int32 inputs that have already been + // converted to 0/-1. + // We can't insert a MIRType::Boolean lane directly - it requires conversion. + if (laneType == MIRType::Boolean) { + MOZ_ASSERT(in->type() == MIRType::Int32, "Boolean SIMD vector requires Int32 lanes."); + return true; + } + + if (in->type() == laneType) + return true; + + MInstruction* replace; + if (laneType == MIRType::Int32) { + replace = MTruncateToInt32::New(alloc, in); + } else { + MOZ_ASSERT(laneType == MIRType::Float32); + replace = MToFloat32::New(alloc, in); + } + + ins->block()->insertBefore(ins, replace); + ins->replaceOperand(Op, replace); + + return replace->typePolicy()->adjustInputs(alloc, replace); +} + +template bool SimdScalarPolicy<0>::staticAdjustInputs(TempAllocator& alloc, MInstruction* def); +template bool SimdScalarPolicy<1>::staticAdjustInputs(TempAllocator& alloc, MInstruction* def); +template bool SimdScalarPolicy<2>::staticAdjustInputs(TempAllocator& alloc, MInstruction* def); +template bool SimdScalarPolicy<3>::staticAdjustInputs(TempAllocator& alloc, MInstruction* def); + template bool BoxPolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) @@ -818,6 +857,75 @@ template bool ObjectPolicy<1>::staticAdjustInputs(TempAllocator& alloc, MInstruc template bool ObjectPolicy<2>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); template bool ObjectPolicy<3>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); +template +bool +SimdSameAsReturnedTypePolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) +{ + MOZ_ASSERT(ins->type() == ins->getOperand(Op)->type()); + return true; +} + +template bool +SimdSameAsReturnedTypePolicy<0>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); +template bool +SimdSameAsReturnedTypePolicy<1>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); + +bool +SimdAllPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const +{ + for (unsigned i = 0, e = ins->numOperands(); i < e; i++) + MOZ_ASSERT(ins->getOperand(i)->type() == ins->typePolicySpecialization()); + return true; +} + +template +bool +SimdPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const +{ + MOZ_ASSERT(ins->typePolicySpecialization() == ins->getOperand(Op)->type()); + return true; +} + +template bool +SimdPolicy<0>::adjustInputs(TempAllocator& alloc, MInstruction* ins) const; + +bool +SimdShufflePolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const +{ + MSimdGeneralShuffle* s = ins->toSimdGeneralShuffle(); + + for (unsigned i = 0; i < s->numVectors(); i++) + MOZ_ASSERT(ins->getOperand(i)->type() == ins->typePolicySpecialization()); + + // Next inputs are the lanes, which need to be int32 + for (unsigned i = 0; i < s->numLanes(); i++) { + MDefinition* in = ins->getOperand(s->numVectors() + i); + if (in->type() == MIRType::Int32) + continue; + + auto* replace = MToNumberInt32::New(alloc, in, IntConversionInputKind::NumbersOnly); + ins->block()->insertBefore(ins, replace); + ins->replaceOperand(s->numVectors() + i, replace); + if (!replace->typePolicy()->adjustInputs(alloc, replace)) + return false; + } + + return true; +} + +bool +SimdSelectPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const +{ + // First input is the mask, which has to be a boolean. + MOZ_ASSERT(IsBooleanSimdType(ins->getOperand(0)->type())); + + // Next inputs are the two vectors of a particular type. + for (unsigned i = 1; i < 3; i++) + MOZ_ASSERT(ins->getOperand(i)->type() == ins->typePolicySpecialization()); + + return true; +} + bool CallPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const { @@ -875,6 +983,13 @@ StoreUnboxedScalarPolicy::adjustValueInput(TempAllocator& alloc, MInstruction* i Scalar::Type writeType, MDefinition* value, int valueOperand) { + // Storing a SIMD value requires a valueOperand that has already been + // SimdUnboxed. See IonBuilder::inlineSimdStore(() + if (Scalar::isSimdType(writeType)) { + MOZ_ASSERT(IsSimdType(value->type())); + return true; + } + MDefinition* curValue = value; // First, ensure the value is int32, boolean, double or Value. // The conversion is based on TypedArrayObjectTemplate::setElementTail. @@ -1155,6 +1270,9 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const _(PowPolicy) \ _(SameValuePolicy) \ _(SignPolicy) \ + _(SimdAllPolicy) \ + _(SimdSelectPolicy) \ + _(SimdShufflePolicy) \ _(StoreTypedArrayHolePolicy) \ _(StoreUnboxedScalarPolicy) \ _(StoreUnboxedObjectOrNullPolicy) \ @@ -1192,6 +1310,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const _(MixPolicy, UnboxedInt32Policy<1>, UnboxedInt32Policy<2>, UnboxedInt32Policy<3>>) \ _(MixPolicy, UnboxedInt32Policy<1>, TruncateToInt32Policy<2>, TruncateToInt32Policy<3> >) \ _(MixPolicy, CacheIdPolicy<1>, NoFloatPolicy<2>>) \ + _(MixPolicy, SimdScalarPolicy<1>, SimdScalarPolicy<2>, SimdScalarPolicy<3> >) \ _(MixPolicy, BoxExceptPolicy<1, MIRType::Object>, CacheIdPolicy<2>>) \ _(MixPolicy, ObjectPolicy<1> >) \ _(MixPolicy, ConvertToStringPolicy<1> >) \ @@ -1211,6 +1330,8 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const _(MixPolicy, StringPolicy<1> >) \ _(MixPolicy, ConvertToStringPolicy<2> >) \ _(MixPolicy, ConvertToStringPolicy<0> >) \ + _(MixPolicy, SimdSameAsReturnedTypePolicy<1> >) \ + _(MixPolicy, SimdScalarPolicy<1> >) \ _(MixPolicy, UnboxedInt32Policy<1> >) \ _(MixPolicy, StringPolicy<1> >) \ _(MixPolicy, BoxPolicy<1> >) \ @@ -1221,6 +1342,9 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) const _(ObjectPolicy<0>) \ _(ObjectPolicy<1>) \ _(ObjectPolicy<3>) \ + _(SimdPolicy<0>) \ + _(SimdSameAsReturnedTypePolicy<0>) \ + _(SimdScalarPolicy<0>) \ _(StringPolicy<0>) diff --git a/js/src/jit/TypePolicy.h b/js/src/jit/TypePolicy.h index edca26ee16b4..3a96dbd3842f 100644 --- a/js/src/jit/TypePolicy.h +++ b/js/src/jit/TypePolicy.h @@ -365,6 +365,67 @@ class ObjectPolicy final : public TypePolicy // a primitive, we use ValueToNonNullObject. typedef ObjectPolicy<0> SingleObjectPolicy; +// Convert an operand to have a type identical to the scalar type of the +// returned type of the instruction. +template +class SimdScalarPolicy final : public TypePolicy +{ + public: + constexpr SimdScalarPolicy() { } + EMPTY_DATA_; + static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* def); + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* def) const override { + return staticAdjustInputs(alloc, def); + } +}; + +class SimdAllPolicy final : public TypePolicy +{ + public: + constexpr SimdAllPolicy () { } + SPECIALIZATION_DATA_; + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override; +}; + +template +class SimdPolicy final : public TypePolicy +{ + public: + constexpr SimdPolicy() { } + SPECIALIZATION_DATA_; + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override; +}; + +class SimdSelectPolicy final : public TypePolicy +{ + public: + constexpr SimdSelectPolicy() { } + SPECIALIZATION_DATA_; + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override; +}; + +class SimdShufflePolicy final : public TypePolicy +{ + public: + constexpr SimdShufflePolicy() { } + SPECIALIZATION_DATA_; + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override; +}; + +// SIMD value-type policy, use the returned type of the instruction to determine +// how to unbox its operand. +template +class SimdSameAsReturnedTypePolicy final : public TypePolicy +{ + public: + constexpr SimdSameAsReturnedTypePolicy() { } + EMPTY_DATA_; + static MOZ_MUST_USE bool staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); + MOZ_MUST_USE bool adjustInputs(TempAllocator& alloc, MInstruction* ins) const override { + return staticAdjustInputs(alloc, ins); + } +}; + template class BoxPolicy final : public TypePolicy { diff --git a/js/src/jit/TypedObjectPrediction.cpp b/js/src/jit/TypedObjectPrediction.cpp index 640cdb296969..ec5472628f3b 100644 --- a/js/src/jit/TypedObjectPrediction.cpp +++ b/js/src/jit/TypedObjectPrediction.cpp @@ -119,6 +119,7 @@ TypedObjectPrediction::ofArrayKind() const switch (kind()) { case type::Scalar: case type::Reference: + case type::Simd: case type::Struct: return false; @@ -206,6 +207,12 @@ TypedObjectPrediction::referenceType() const return extractType(); } +SimdType +TypedObjectPrediction::simdType() const +{ + return descr().as().type(); +} + bool TypedObjectPrediction::hasKnownArrayLength(int32_t* length) const { diff --git a/js/src/jit/TypedObjectPrediction.h b/js/src/jit/TypedObjectPrediction.h index c64cc9508d6a..c809f0903a1e 100644 --- a/js/src/jit/TypedObjectPrediction.h +++ b/js/src/jit/TypedObjectPrediction.h @@ -165,10 +165,11 @@ class TypedObjectPrediction { ////////////////////////////////////////////////////////////////////// // Simple operations // - // Only valid when |kind()| is Scalar or Reference. + // Only valid when |kind()| is Scalar, Reference, or Simd (as appropriate). Scalar::Type scalarType() const; ReferenceType referenceType() const; + SimdType simdType() const; /////////////////////////////////////////////////////////////////////////// // Queries valid only for arrays. diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index ca864f42e42e..3b23961124d5 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -3156,14 +3156,248 @@ CodeGenerator::visitWasmAtomicExchangeI64(LWasmAtomicExchangeI64* lir) masm.atomicExchange64(Synchronization::Full(), addr, value, out); } +void +CodeGenerator::visitSimdSplatX4(LSimdSplatX4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Int(LSimd128Int* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Float(LSimd128Float* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementI(LSimdExtractElementI* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementF(LSimdExtractElementF* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx4(LSimdBinaryCompIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompFx4(LSimdBinaryCompFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx4(LSimdBinaryArithIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithFx4(LSimdBinaryArithFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryBitwise(LSimdBinaryBitwise* lir) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitNearbyInt(LNearbyInt*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdShift(LSimdShift*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitNearbyIntF(LNearbyIntF*) { MOZ_CRASH("NYI"); } + +void +CodeGenerator::visitSimdSelect(LSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdAllTrue(LSimdAllTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdAnyTrue(LSimdAnyTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdShuffle(LSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSplatX8(LSimdSplatX8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSplatX16(LSimdSplatX16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleF(LSimdSwizzleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleI(LSimdSwizzleI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdShuffleX4(LSimdShuffleX4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx8(LSimdBinaryCompIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithFx4(LSimdUnaryArithFx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx4(LSimdUnaryArithIx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx8(LSimdUnaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitFloat32x4ToInt32x4(LFloat32x4ToInt32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitInt32x4ToFloat32x4(LInt32x4ToFloat32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx8(LSimdBinaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx16(LSimdBinaryCompIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementF(LSimdInsertElementF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementI(LSimdInsertElementI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx16(LSimdUnaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx16(LSimdBinaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementB(LSimdExtractElementB*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleF(LSimdGeneralShuffleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleI(LSimdGeneralShuffleI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdReinterpretCast(LSimdReinterpretCast*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinarySaturating(LSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementU2D(LSimdExtractElementU2D*) +{ + MOZ_CRASH("NYI"); +} diff --git a/js/src/jit/arm/Lowering-arm.cpp b/js/src/jit/arm/Lowering-arm.cpp index aaf1aec4f7f7..547073cc80d2 100644 --- a/js/src/jit/arm/Lowering-arm.cpp +++ b/js/src/jit/arm/Lowering-arm.cpp @@ -1058,3 +1058,63 @@ LIRGenerator::visitSignExtendInt64(MSignExtendInt64* ins) { defineInt64(new(alloc()) LSignExtendInt64(useInt64RegisterAtStart(ins->input())), ins); } + +void +LIRGenerator::visitSimdInsertElement(MSimdInsertElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdExtractElement(MSimdExtractElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinaryArith(MSimdBinaryArith*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSelect(MSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSplat(MSimdSplat*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdValueX4(MSimdValueX4*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinarySaturating(MSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSwizzle(MSimdSwizzle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdShuffle(MSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdGeneralShuffle(MSimdGeneralShuffle*) +{ + MOZ_CRASH("NYI"); +} diff --git a/js/src/jit/arm/Lowering-arm.h b/js/src/jit/arm/Lowering-arm.h index 871edd9810eb..27913ba71657 100644 --- a/js/src/jit/arm/Lowering-arm.h +++ b/js/src/jit/arm/Lowering-arm.h @@ -63,6 +63,17 @@ class LIRGeneratorARM : public LIRGeneratorShared void lowerForFPU(LInstructionHelper<1, 2, Temps>* ins, MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir, MDefinition* lhs, MDefinition* rhs); void lowerTruncateDToInt32(MTruncateToInt32* ins); diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 97f40753e54f..8d5f1d923f0e 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1062,6 +1062,39 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void loadPrivate(const Address& address, Register dest); + void loadInt32x1(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x1(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x4(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + + void loadFloat32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeFloat32x4(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + + void loadAlignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + void loadDouble(const Address& addr, FloatRegister dest); void loadDouble(const BaseIndex& src, FloatRegister dest); diff --git a/js/src/jit/arm64/CodeGenerator-arm64.cpp b/js/src/jit/arm64/CodeGenerator-arm64.cpp index 11eed2018ea5..42ae6082a5b5 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.cpp +++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp @@ -847,6 +847,66 @@ CodeGenerator::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayEle } } +void +CodeGenerator::visitSimdSplatX4(LSimdSplatX4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Int(LSimd128Int* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Float(LSimd128Float* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementI(LSimdExtractElementI* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementF(LSimdExtractElementF* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx4(LSimdBinaryCompIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompFx4(LSimdBinaryCompFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx4(LSimdBinaryArithIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithFx4(LSimdBinaryArithFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryBitwise(LSimdBinaryBitwise* lir) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitAddI64(LAddI64*) { @@ -949,6 +1009,12 @@ CodeGenerator::visitRotateI64(LRotateI64*) MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdShift(LSimdShift*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmStore(LWasmStore*) { @@ -967,18 +1033,66 @@ CodeGenerator::visitNearbyIntF(LNearbyIntF*) MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdSelect(LSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmSelect(LWasmSelect*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdAllTrue(LSimdAllTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdAnyTrue(LSimdAnyTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdShuffle(LSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSplatX8(LSimdSplatX8*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmLoadI64(LWasmLoadI64*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdSplatX16(LSimdSplatX16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleF(LSimdSwizzleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleI(LSimdSwizzleI*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmStoreI64(LWasmStoreI64*) { @@ -991,6 +1105,12 @@ CodeGenerator::visitMemoryBarrier(LMemoryBarrier*) MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdShuffleX4(LSimdShuffleX4*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitSoftUDivOrMod(LSoftUDivOrMod*) { @@ -1039,30 +1159,144 @@ CodeGenerator::visitWrapInt64ToInt32(LWrapInt64ToInt32*) MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdBinaryCompIx8(LSimdBinaryCompIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithFx4(LSimdUnaryArithFx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx4(LSimdUnaryArithIx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx8(LSimdUnaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitExtendInt32ToInt64(LExtendInt32ToInt64*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitFloat32x4ToInt32x4(LFloat32x4ToInt32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitInt32x4ToFloat32x4(LInt32x4ToFloat32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx8(LSimdBinaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx16(LSimdBinaryCompIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementF(LSimdInsertElementF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementI(LSimdInsertElementI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx16(LSimdUnaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitCompareI64AndBranch(LCompareI64AndBranch*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx16(LSimdBinaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementB(LSimdExtractElementB*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleF(LSimdGeneralShuffleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleI(LSimdGeneralShuffleI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdReinterpretCast(LSimdReinterpretCast*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmTruncateToInt32(LWasmTruncateToInt32*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdBinarySaturating(LSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmReinterpretToI64(LWasmReinterpretToI64*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdExtractElementU2D(LSimdExtractElementU2D*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitWasmAtomicExchangeHeap(LWasmAtomicExchangeHeap*) { diff --git a/js/src/jit/arm64/Lowering-arm64.cpp b/js/src/jit/arm64/Lowering-arm64.cpp index 9b015c935d51..cb9ba15d9d9a 100644 --- a/js/src/jit/arm64/Lowering-arm64.cpp +++ b/js/src/jit/arm64/Lowering-arm64.cpp @@ -402,3 +402,63 @@ LIRGenerator::visitSignExtendInt64(MSignExtendInt64* ins) { MOZ_CRASH("NYI"); } + +void +LIRGenerator::visitSimdInsertElement(MSimdInsertElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdExtractElement(MSimdExtractElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinaryArith(MSimdBinaryArith*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSelect(MSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSplat(MSimdSplat*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdValueX4(MSimdValueX4*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinarySaturating(MSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSwizzle(MSimdSwizzle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdShuffle(MSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdGeneralShuffle(MSimdGeneralShuffle*) +{ + MOZ_CRASH("NYI"); +} diff --git a/js/src/jit/arm64/Lowering-arm64.h b/js/src/jit/arm64/Lowering-arm64.h index abd208d03fe5..46ce9a28ea7d 100644 --- a/js/src/jit/arm64/Lowering-arm64.h +++ b/js/src/jit/arm64/Lowering-arm64.h @@ -63,6 +63,18 @@ class LIRGeneratorARM64 : public LIRGeneratorShared void lowerForFPU(LInstructionHelper<1, 2, Temps>* ins, MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + + void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir, MDefinition* lhs, MDefinition* rhs); void lowerTruncateDToInt32(MTruncateToInt32* ins); diff --git a/js/src/jit/arm64/MacroAssembler-arm64.cpp b/js/src/jit/arm64/MacroAssembler-arm64.cpp index 06febc76d9e7..59d8dd8b2c68 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -319,6 +319,10 @@ MacroAssemblerCompat::wasmLoadImpl(const wasm::MemoryAccessDesc& access, Registe break; case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: + case Scalar::Float32x4: + case Scalar::Int32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: MOZ_CRASH("unexpected array type"); } @@ -367,6 +371,10 @@ MacroAssemblerCompat::wasmStoreImpl(const wasm::MemoryAccessDesc& access, AnyReg case Scalar::Float64: Str(SelectFPReg(valany, val64, 64), dstAddr); break; + case Scalar::Float32x4: + case Scalar::Int32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/arm64/MacroAssembler-arm64.h b/js/src/jit/arm64/MacroAssembler-arm64.h index 7ce791776da1..0c66a445fcd2 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.h +++ b/js/src/jit/arm64/MacroAssembler-arm64.h @@ -921,6 +921,44 @@ class MacroAssemblerCompat : public vixl::MacroAssembler storePtr(src.reg, address); } + // SIMD. + void loadInt32x1(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x1(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x4(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Int(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Int(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Int(FloatRegister src, const BaseIndex& addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister dest, const Address& addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister dest, const BaseIndex& addr) { MOZ_CRASH("NYI"); } + + void loadFloat32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeFloat32x4(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + + void loadAlignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Float(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Float(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Float(FloatRegister src, const BaseIndex& addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister dest, const Address& addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister dest, const BaseIndex& addr) { MOZ_CRASH("NYI"); } + // StackPointer manipulation. inline void addToStackPtr(Register src); inline void addToStackPtr(Imm32 imm); diff --git a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp index 64b79a900fec..37fde0e9fe8d 100644 --- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp +++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp @@ -2552,14 +2552,248 @@ CodeGenerator::visitWasmAtomicBinopI64(LWasmAtomicBinopI64* lir) output); } +void +CodeGenerator::visitSimdSplatX4(LSimdSplatX4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Int(LSimd128Int* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimd128Float(LSimd128Float* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementI(LSimdExtractElementI* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementF(LSimdExtractElementF* ins) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx4(LSimdBinaryCompIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompFx4(LSimdBinaryCompFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx4(LSimdBinaryArithIx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithFx4(LSimdBinaryArithFx4* lir) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryBitwise(LSimdBinaryBitwise* lir) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitNearbyInt(LNearbyInt*) { MOZ_CRASH("NYI"); } +void +CodeGenerator::visitSimdShift(LSimdShift*) +{ + MOZ_CRASH("NYI"); +} + void CodeGenerator::visitNearbyIntF(LNearbyIntF*) { MOZ_CRASH("NYI"); } + +void +CodeGenerator::visitSimdSelect(LSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdAllTrue(LSimdAllTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdAnyTrue(LSimdAnyTrue*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdShuffle(LSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSplatX8(LSimdSplatX8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSplatX16(LSimdSplatX16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleF(LSimdSwizzleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdSwizzleI(LSimdSwizzleI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdShuffleX4(LSimdShuffleX4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx8(LSimdBinaryCompIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithFx4(LSimdUnaryArithFx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx4(LSimdUnaryArithIx4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx8(LSimdUnaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitFloat32x4ToInt32x4(LFloat32x4ToInt32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitInt32x4ToFloat32x4(LInt32x4ToFloat32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx8(LSimdBinaryArithIx8*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryCompIx16(LSimdBinaryCompIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementF(LSimdInsertElementF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdInsertElementI(LSimdInsertElementI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdUnaryArithIx16(LSimdUnaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinaryArithIx16(LSimdBinaryArithIx16*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementB(LSimdExtractElementB*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleF(LSimdGeneralShuffleF*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdGeneralShuffleI(LSimdGeneralShuffleI*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdReinterpretCast(LSimdReinterpretCast*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdBinarySaturating(LSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + +void +CodeGenerator::visitSimdExtractElementU2D(LSimdExtractElementU2D*) +{ + MOZ_CRASH("NYI"); +} diff --git a/js/src/jit/mips-shared/Lowering-mips-shared.cpp b/js/src/jit/mips-shared/Lowering-mips-shared.cpp index e46678c3807d..b935a102be1b 100644 --- a/js/src/jit/mips-shared/Lowering-mips-shared.cpp +++ b/js/src/jit/mips-shared/Lowering-mips-shared.cpp @@ -819,3 +819,63 @@ LIRGenerator::visitSignExtendInt64(MSignExtendInt64* ins) { defineInt64(new(alloc()) LSignExtendInt64(useInt64RegisterAtStart(ins->input())), ins); } + +void +LIRGenerator::visitSimdInsertElement(MSimdInsertElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdExtractElement(MSimdExtractElement*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinaryArith(MSimdBinaryArith*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSelect(MSimdSelect*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSplat(MSimdSplat*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdValueX4(MSimdValueX4*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdBinarySaturating(MSimdBinarySaturating*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdSwizzle(MSimdSwizzle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdShuffle(MSimdShuffle*) +{ + MOZ_CRASH("NYI"); +} + +void +LIRGenerator::visitSimdGeneralShuffle(MSimdGeneralShuffle*) +{ + MOZ_CRASH("NYI"); +} diff --git a/js/src/jit/mips-shared/Lowering-mips-shared.h b/js/src/jit/mips-shared/Lowering-mips-shared.h index 43fedfe9cf0d..4378596db8c1 100644 --- a/js/src/jit/mips-shared/Lowering-mips-shared.h +++ b/js/src/jit/mips-shared/Lowering-mips-shared.h @@ -50,6 +50,17 @@ class LIRGeneratorMIPSShared : public LIRGeneratorShared void lowerForFPU(LInstructionHelper<1, 2, Temps>* ins, MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) + { + return lowerForFPU(ins, mir, lhs, rhs); + } + void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir, MDefinition* lhs, MDefinition* rhs); void lowerDivI(MDiv* div); diff --git a/js/src/jit/mips32/MacroAssembler-mips32.h b/js/src/jit/mips32/MacroAssembler-mips32.h index 79763960f856..000715f6fef2 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32.h +++ b/js/src/jit/mips32/MacroAssembler-mips32.h @@ -628,6 +628,39 @@ class MacroAssemblerMIPSCompat : public MacroAssemblerMIPS void loadPrivate(const Address& address, Register dest); + void loadInt32x1(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x1(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x4(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + + void loadFloat32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeFloat32x4(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + + void loadAlignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + void loadUnalignedDouble(const wasm::MemoryAccessDesc& access, const BaseIndex& src, Register temp, FloatRegister dest); diff --git a/js/src/jit/mips64/MacroAssembler-mips64.h b/js/src/jit/mips64/MacroAssembler-mips64.h index f8eba1fcca51..fdf0043f5f28 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64.h +++ b/js/src/jit/mips64/MacroAssembler-mips64.h @@ -644,6 +644,39 @@ class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64 void loadPrivate(const Address& address, Register dest); + void loadInt32x1(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x1(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x4(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void loadAlignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Int(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Int(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + + void loadFloat32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x4(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeFloat32x4(FloatRegister src, const Address& addr) { MOZ_CRASH("NYI"); } + + void loadAlignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedSimd128Float(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedSimd128Float(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + void loadUnalignedDouble(const wasm::MemoryAccessDesc& access, const BaseIndex& src, Register temp, FloatRegister dest); void loadUnalignedFloat32(const wasm::MemoryAccessDesc& access, const BaseIndex& src, diff --git a/js/src/jit/none/Lowering-none.h b/js/src/jit/none/Lowering-none.h index b3aa536641af..bd2d262e8f80 100644 --- a/js/src/jit/none/Lowering-none.h +++ b/js/src/jit/none/Lowering-none.h @@ -46,6 +46,14 @@ class LIRGeneratorNone : public LIRGeneratorShared void lowerForMulInt64(LMulI64*, MMul*, MDefinition*, MDefinition* v = nullptr) { MOZ_CRASH(); } template void lowerForShiftInt64(T, MDefinition*, MDefinition*, MDefinition* v = nullptr) { MOZ_CRASH(); } + void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) { + MOZ_CRASH(); + } + void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs) { + MOZ_CRASH(); + } void lowerForBitAndAndBranch(LBitAndAndBranch*, MInstruction*, MDefinition*, MDefinition*) { MOZ_CRASH(); diff --git a/js/src/jit/none/MacroAssembler-none.h b/js/src/jit/none/MacroAssembler-none.h index a8fba55c739f..b65836b597ae 100644 --- a/js/src/jit/none/MacroAssembler-none.h +++ b/js/src/jit/none/MacroAssembler-none.h @@ -275,10 +275,21 @@ class MacroAssemblerNone : public Assembler template void move64(T, S) { MOZ_CRASH(); } template CodeOffset movWithPatch(T, Register) { MOZ_CRASH(); } + template void loadInt32x1(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadInt32x2(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadInt32x3(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadInt32x4(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadFloat32x3(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadFloat32x4(T, FloatRegister dest) { MOZ_CRASH(); } + template void loadPtr(T, Register) { MOZ_CRASH(); } template void load32(T, Register) { MOZ_CRASH(); } template void loadFloat32(T, FloatRegister) { MOZ_CRASH(); } template void loadDouble(T, FloatRegister) { MOZ_CRASH(); } + template void loadAlignedSimd128Int(T, FloatRegister) { MOZ_CRASH(); } + template void loadUnalignedSimd128Int(T, FloatRegister) { MOZ_CRASH(); } + template void loadAlignedSimd128Float(T, FloatRegister) { MOZ_CRASH(); } + template void loadUnalignedSimd128Float(T, FloatRegister) { MOZ_CRASH(); } template void loadPrivate(T, Register) { MOZ_CRASH(); } template void load8SignExtend(T, Register) { MOZ_CRASH(); } template void load8ZeroExtend(T, Register) { MOZ_CRASH(); } @@ -291,8 +302,18 @@ class MacroAssemblerNone : public Assembler template void store32_NoSecondScratch(T, S) { MOZ_CRASH(); } template void storeFloat32(T, S) { MOZ_CRASH(); } template void storeDouble(T, S) { MOZ_CRASH(); } + template void storeAlignedSimd128Int(T, S) { MOZ_CRASH(); } + template void storeUnalignedSimd128Int(T, S) { MOZ_CRASH(); } + template void storeAlignedSimd128Float(T, S) { MOZ_CRASH(); } + template void storeUnalignedSimd128Float(T, S) { MOZ_CRASH(); } template void store8(T, S) { MOZ_CRASH(); } template void store16(T, S) { MOZ_CRASH(); } + template void storeInt32x1(T, S) { MOZ_CRASH(); } + template void storeInt32x2(T, S) { MOZ_CRASH(); } + template void storeInt32x3(T, S) { MOZ_CRASH(); } + template void storeInt32x4(T, S) { MOZ_CRASH(); } + template void storeFloat32x3(T, S) { MOZ_CRASH(); } + template void storeFloat32x4(T, S) { MOZ_CRASH(); } template void store64(T, S) { MOZ_CRASH(); } template void computeEffectiveAddress(T, Register) { MOZ_CRASH(); } diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 66814ac1f55e..05adfea10ea2 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -791,29 +791,39 @@ class MemoryAccessDesc uint32_t offset_; uint32_t align_; Scalar::Type type_; + unsigned numSimdElems_; jit::Synchronization sync_; wasm::BytecodeOffset trapOffset_; public: explicit MemoryAccessDesc(Scalar::Type type, uint32_t align, uint32_t offset, - BytecodeOffset trapOffset, + BytecodeOffset trapOffset, unsigned numSimdElems = 0, const jit::Synchronization& sync = jit::Synchronization::None()) : offset_(offset), align_(align), type_(type), + numSimdElems_(numSimdElems), sync_(sync), trapOffset_(trapOffset) { + MOZ_ASSERT(Scalar::isSimdType(type) == (numSimdElems > 0)); + MOZ_ASSERT(numSimdElems <= jit::ScalarTypeToLength(type)); MOZ_ASSERT(mozilla::IsPowerOfTwo(align)); } uint32_t offset() const { return offset_; } uint32_t align() const { return align_; } Scalar::Type type() const { return type_; } - unsigned byteSize() const { return Scalar::byteSize(type()); } + unsigned byteSize() const { + return Scalar::isSimdType(type()) + ? Scalar::scalarByteSize(type()) * numSimdElems() + : Scalar::byteSize(type()); + } + unsigned numSimdElems() const { MOZ_ASSERT(isSimd()); return numSimdElems_; } const jit::Synchronization& sync() const { return sync_; } BytecodeOffset trapOffset() const { return trapOffset_; } bool isAtomic() const { return !sync_.isNone(); } + bool isSimd() const { return Scalar::isSimdType(type_); } void clearOffset() { offset_ = 0; } void setOffset(uint32_t offset) { offset_ = offset; } diff --git a/js/src/jit/shared/CodeGenerator-shared-inl.h b/js/src/jit/shared/CodeGenerator-shared-inl.h index 6647039dbfa7..5ffbbbc59a0c 100644 --- a/js/src/jit/shared/CodeGenerator-shared-inl.h +++ b/js/src/jit/shared/CodeGenerator-shared-inl.h @@ -378,6 +378,10 @@ CodeGeneratorShared::verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, b break; case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: op = OtherOperand(ToFloatRegister(alloc).encoding()); break; case Scalar::Uint8Clamped: diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 41bec53a14e7..5deeebe9e4b9 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -89,10 +89,17 @@ CodeGeneratorShared::CodeGeneratorShared(MIRGenerator* gen, LIRGraph* graph, Mac MOZ_ASSERT(graph->argumentSlotCount() == 0); frameDepth_ += gen->wasmMaxStackArgBytes(); - static_assert(!SupportsSimd, "we need padding so that local slots are SIMD-aligned and " - "the stack must be kept SIMD-aligned too."); + if (gen->usesSimd()) { + // If the function uses any SIMD then we may need to insert padding + // so that local slots are aligned for SIMD. + frameInitialAdjustment_ = ComputeByteAlignment(sizeof(wasm::Frame), WasmStackAlignment); + frameDepth_ += frameInitialAdjustment_; - if (gen->needsStaticStackAlignment()) { + // Keep the stack aligned. Some SIMD sequences build values on the + // stack and need the stack aligned. + frameDepth_ += ComputeByteAlignment(sizeof(wasm::Frame) + frameDepth_, + WasmStackAlignment); + } else if (gen->needsStaticStackAlignment()) { // An MWasmCall does not align the stack pointer at calls sites but // instead relies on the a priori stack adjustment. This must be the // last adjustment of frameDepth_. diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index b218b56dda93..ef6d84628a98 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -161,6 +161,672 @@ class LMoveGroup : public LInstructionHelper<0, 0, 0> } }; + +// Constructs a SIMD object (value type) based on the MIRType of its input. +class LSimdBox : public LInstructionHelper<1, 1, 1> +{ + public: + LIR_HEADER(SimdBox) + + explicit LSimdBox(const LAllocation& simd, const LDefinition& temp) + : LInstructionHelper(classOpcode) + { + setOperand(0, simd); + setTemp(0, temp); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MSimdBox* mir() const { + return mir_->toSimdBox(); + } +}; + +class LSimdUnbox : public LInstructionHelper<1, 1, 1> +{ + public: + LIR_HEADER(SimdUnbox) + + LSimdUnbox(const LAllocation& obj, const LDefinition& temp) + : LInstructionHelper(classOpcode) + { + setOperand(0, obj); + setTemp(0, temp); + } + + const LDefinition* temp() { + return getTemp(0); + } + + MSimdUnbox* mir() const { + return mir_->toSimdUnbox(); + } +}; + +// Constructs a SIMD value with 16 equal components (int8x16). +class LSimdSplatX16 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdSplatX16) + explicit LSimdSplatX16(const LAllocation& v) + : LInstructionHelper(classOpcode) + { + setOperand(0, v); + } + + MSimdSplat* mir() const { + return mir_->toSimdSplat(); + } +}; + +// Constructs a SIMD value with 8 equal components (int16x8). +class LSimdSplatX8 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdSplatX8) + explicit LSimdSplatX8(const LAllocation& v) + : LInstructionHelper(classOpcode) + { + setOperand(0, v); + } + + MSimdSplat* mir() const { + return mir_->toSimdSplat(); + } +}; + +// Constructs a SIMD value with 4 equal components (e.g. int32x4, float32x4). +class LSimdSplatX4 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdSplatX4) + explicit LSimdSplatX4(const LAllocation& v) + : LInstructionHelper(classOpcode) + { + setOperand(0, v); + } + + MSimdSplat* mir() const { + return mir_->toSimdSplat(); + } +}; + +// Reinterpret the bits of a SIMD value with a different type. +class LSimdReinterpretCast : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdReinterpretCast) + explicit LSimdReinterpretCast(const LAllocation& v) + : LInstructionHelper(classOpcode) + { + setOperand(0, v); + } + + MSimdReinterpretCast* mir() const { + return mir_->toSimdReinterpretCast(); + } +}; + +class LSimdExtractElementBase : public LInstructionHelper<1, 1, 0> +{ + protected: + LSimdExtractElementBase(Opcode opcode, const LAllocation& base) + : LInstructionHelper(opcode) + { + setOperand(0, base); + } + + public: + const LAllocation* getBase() { + return getOperand(0); + } + MSimdExtractElement* mir() const { + return mir_->toSimdExtractElement(); + } +}; + +// Extracts an element from a given SIMD bool32x4 lane. +class LSimdExtractElementB : public LSimdExtractElementBase +{ + public: + LIR_HEADER(SimdExtractElementB); + explicit LSimdExtractElementB(const LAllocation& base) + : LSimdExtractElementBase(classOpcode, base) + {} +}; + +// Extracts an element from a given SIMD int32x4 lane. +class LSimdExtractElementI : public LSimdExtractElementBase +{ + public: + LIR_HEADER(SimdExtractElementI); + explicit LSimdExtractElementI(const LAllocation& base) + : LSimdExtractElementBase(classOpcode, base) + {} +}; + +// Extracts an element from a given SIMD float32x4 lane. +class LSimdExtractElementF : public LSimdExtractElementBase +{ + public: + LIR_HEADER(SimdExtractElementF); + explicit LSimdExtractElementF(const LAllocation& base) + : LSimdExtractElementBase(classOpcode, base) + {} +}; + +// Extracts an element from an Uint32x4 SIMD vector, converts to double. +class LSimdExtractElementU2D : public LInstructionHelper<1, 1, 1> +{ + public: + LIR_HEADER(SimdExtractElementU2D); + LSimdExtractElementU2D(const LAllocation& base, const LDefinition& temp) + : LInstructionHelper(classOpcode) + { + setOperand(0, base); + setTemp(0, temp); + } + MSimdExtractElement* mir() const { + return mir_->toSimdExtractElement(); + } + const LDefinition* temp() { + return getTemp(0); + } +}; + + +class LSimdInsertElementBase : public LInstructionHelper<1, 2, 0> +{ + protected: + LSimdInsertElementBase(Opcode opcode, const LAllocation& vec, const LAllocation& val) + : LInstructionHelper(opcode) + { + setOperand(0, vec); + setOperand(1, val); + } + + public: + const LAllocation* vector() { + return getOperand(0); + } + const LAllocation* value() { + return getOperand(1); + } + unsigned lane() const { + return mir_->toSimdInsertElement()->lane(); + } + unsigned length() const { + return SimdTypeToLength(mir_->toSimdInsertElement()->type()); + } +}; + +// Replace an element from a given SIMD integer or boolean lane with a given value. +// The value inserted into a boolean lane should be 0 or -1. +class LSimdInsertElementI : public LSimdInsertElementBase +{ + public: + LIR_HEADER(SimdInsertElementI); + LSimdInsertElementI(const LAllocation& vec, const LAllocation& val) + : LSimdInsertElementBase(classOpcode, vec, val) + {} +}; + +// Replace an element from a given SIMD float32x4 lane with a given value. +class LSimdInsertElementF : public LSimdInsertElementBase +{ + public: + LIR_HEADER(SimdInsertElementF); + LSimdInsertElementF(const LAllocation& vec, const LAllocation& val) + : LSimdInsertElementBase(classOpcode, vec, val) + {} +}; + +// Base class for both int32x4 and float32x4 shuffle instructions. +class LSimdSwizzleBase : public LInstructionHelper<1, 1, 1> +{ + public: + LSimdSwizzleBase(Opcode opcode, const LAllocation& base) + : LInstructionHelper(opcode) + { + setOperand(0, base); + } + + const LAllocation* getBase() { + return getOperand(0); + } + + unsigned numLanes() const { return mir_->toSimdSwizzle()->numLanes(); } + uint32_t lane(unsigned i) const { return mir_->toSimdSwizzle()->lane(i); } + + bool lanesMatch(uint32_t x, uint32_t y, uint32_t z, uint32_t w) const { + return mir_->toSimdSwizzle()->lanesMatch(x, y, z, w); + } +}; + +// Shuffles a int32x4 into another int32x4 vector. +class LSimdSwizzleI : public LSimdSwizzleBase +{ + public: + LIR_HEADER(SimdSwizzleI); + explicit LSimdSwizzleI(const LAllocation& base) : LSimdSwizzleBase(classOpcode, base) + {} +}; +// Shuffles a float32x4 into another float32x4 vector. +class LSimdSwizzleF : public LSimdSwizzleBase +{ + public: + LIR_HEADER(SimdSwizzleF); + explicit LSimdSwizzleF(const LAllocation& base) : LSimdSwizzleBase(classOpcode, base) + {} +}; + +class LSimdGeneralShuffleBase : public LVariadicInstruction<1, 1> +{ + public: + LSimdGeneralShuffleBase(LNode::Opcode opcode, uint32_t numOperands, const LDefinition& temp) + : LVariadicInstruction<1, 1>(opcode, numOperands) + { + setTemp(0, temp); + } + const LAllocation* vector(unsigned i) { + MOZ_ASSERT(i < mir()->numVectors()); + return getOperand(i); + } + const LAllocation* lane(unsigned i) { + MOZ_ASSERT(i < mir()->numLanes()); + return getOperand(mir()->numVectors() + i); + } + const LDefinition* temp() { + return getTemp(0); + } + MSimdGeneralShuffle* mir() const { + return mir_->toSimdGeneralShuffle(); + } +}; + +class LSimdGeneralShuffleI : public LSimdGeneralShuffleBase +{ + public: + LIR_HEADER(SimdGeneralShuffleI); + + LSimdGeneralShuffleI(uint32_t numOperands, const LDefinition& temp) + : LSimdGeneralShuffleBase(classOpcode, numOperands, temp) + {} +}; + +class LSimdGeneralShuffleF : public LSimdGeneralShuffleBase +{ + public: + LIR_HEADER(SimdGeneralShuffleF); + + LSimdGeneralShuffleF(uint32_t numOperands, const LDefinition& temp) + : LSimdGeneralShuffleBase(classOpcode, numOperands, temp) + {} +}; + +// Base class for both int32x4 and float32x4 shuffle instructions. +class LSimdShuffleX4 : public LInstructionHelper<1, 2, 1> +{ + public: + LIR_HEADER(SimdShuffleX4); + LSimdShuffleX4() + : LInstructionHelper(classOpcode) + {} + + const LAllocation* lhs() { + return getOperand(0); + } + const LAllocation* rhs() { + return getOperand(1); + } + const LDefinition* temp() { + return getTemp(0); + } + + uint32_t lane(unsigned i) const { return mir_->toSimdShuffle()->lane(i); } + + bool lanesMatch(uint32_t x, uint32_t y, uint32_t z, uint32_t w) const { + return mir_->toSimdShuffle()->lanesMatch(x, y, z, w); + } +}; + +// Remaining shuffles (8x16, 16x8). +class LSimdShuffle : public LInstructionHelper<1, 2, 1> +{ + public: + LIR_HEADER(SimdShuffle); + LSimdShuffle() + : LInstructionHelper(classOpcode) + {} + + const LAllocation* lhs() { + return getOperand(0); + } + const LAllocation* rhs() { + return getOperand(1); + } + const LDefinition* temp() { + return getTemp(0); + } + + unsigned numLanes() const { return mir_->toSimdShuffle()->numLanes(); } + unsigned lane(unsigned i) const { return mir_->toSimdShuffle()->lane(i); } +}; + +// Binary SIMD comparison operation between two SIMD operands +class LSimdBinaryComp: public LInstructionHelper<1, 2, 0> +{ + protected: + explicit LSimdBinaryComp(LNode::Opcode opcode) + : LInstructionHelper<1, 2, 0>(opcode) + {} + + public: + const LAllocation* lhs() { + return getOperand(0); + } + const LAllocation* rhs() { + return getOperand(1); + } + MSimdBinaryComp::Operation operation() const { + return mir_->toSimdBinaryComp()->operation(); + } + const char* extraName() const { + return MSimdBinaryComp::OperationName(operation()); + } +}; + +// Binary SIMD comparison operation between two Int8x16 operands. +class LSimdBinaryCompIx16 : public LSimdBinaryComp +{ + public: + LIR_HEADER(SimdBinaryCompIx16); + LSimdBinaryCompIx16() : LSimdBinaryComp(classOpcode) {} +}; + +// Binary SIMD comparison operation between two Int16x8 operands. +class LSimdBinaryCompIx8 : public LSimdBinaryComp +{ + public: + LIR_HEADER(SimdBinaryCompIx8); + LSimdBinaryCompIx8() : LSimdBinaryComp(classOpcode) {} +}; + +// Binary SIMD comparison operation between two Int32x4 operands. +class LSimdBinaryCompIx4 : public LSimdBinaryComp +{ + public: + LIR_HEADER(SimdBinaryCompIx4); + LSimdBinaryCompIx4() : LSimdBinaryComp(classOpcode) {} +}; + +// Binary SIMD comparison operation between two Float32x4 operands +class LSimdBinaryCompFx4 : public LSimdBinaryComp +{ + public: + LIR_HEADER(SimdBinaryCompFx4); + LSimdBinaryCompFx4() : LSimdBinaryComp(classOpcode) {} +}; + +// Binary SIMD arithmetic operation between two SIMD operands +class LSimdBinaryArith : public LInstructionHelper<1, 2, 1> +{ + public: + explicit LSimdBinaryArith(LNode::Opcode opcode) + : LInstructionHelper<1, 2, 1>(opcode) + {} + + const LAllocation* lhs() { + return this->getOperand(0); + } + const LAllocation* rhs() { + return this->getOperand(1); + } + const LDefinition* temp() { + return getTemp(0); + } + + MSimdBinaryArith::Operation operation() const { + return this->mir_->toSimdBinaryArith()->operation(); + } + const char* extraName() const { + return MSimdBinaryArith::OperationName(operation()); + } +}; + +// Binary SIMD arithmetic operation between two Int8x16 operands +class LSimdBinaryArithIx16 : public LSimdBinaryArith +{ + public: + LIR_HEADER(SimdBinaryArithIx16); + LSimdBinaryArithIx16() : LSimdBinaryArith(classOpcode) {} +}; + +// Binary SIMD arithmetic operation between two Int16x8 operands +class LSimdBinaryArithIx8 : public LSimdBinaryArith +{ + public: + LIR_HEADER(SimdBinaryArithIx8); + LSimdBinaryArithIx8() : LSimdBinaryArith(classOpcode) {} +}; + +// Binary SIMD arithmetic operation between two Int32x4 operands +class LSimdBinaryArithIx4 : public LSimdBinaryArith +{ + public: + LIR_HEADER(SimdBinaryArithIx4); + LSimdBinaryArithIx4() : LSimdBinaryArith(classOpcode) {} +}; + +// Binary SIMD arithmetic operation between two Float32x4 operands +class LSimdBinaryArithFx4 : public LSimdBinaryArith +{ + public: + LIR_HEADER(SimdBinaryArithFx4); + LSimdBinaryArithFx4() : LSimdBinaryArith(classOpcode) {} +}; + +// Binary SIMD saturating arithmetic operation between two SIMD operands +class LSimdBinarySaturating : public LInstructionHelper<1, 2, 0> +{ + public: + LIR_HEADER(SimdBinarySaturating); + LSimdBinarySaturating() + : LInstructionHelper(classOpcode) + {} + + const LAllocation* lhs() { + return this->getOperand(0); + } + const LAllocation* rhs() { + return this->getOperand(1); + } + + MSimdBinarySaturating::Operation operation() const { + return this->mir_->toSimdBinarySaturating()->operation(); + } + SimdSign signedness() const { + return this->mir_->toSimdBinarySaturating()->signedness(); + } + MIRType type() const { + return mir_->type(); + } + const char* extraName() const { + return MSimdBinarySaturating::OperationName(operation()); + } +}; + +// Unary SIMD arithmetic operation on a SIMD operand +class LSimdUnaryArith : public LInstructionHelper<1, 1, 0> +{ + public: + LSimdUnaryArith(LNode::Opcode opcode, const LAllocation& in) + : LInstructionHelper(opcode) + { + setOperand(0, in); + } + MSimdUnaryArith::Operation operation() const { + return mir_->toSimdUnaryArith()->operation(); + } +}; + +// Unary SIMD arithmetic operation on a Int8x16 operand +class LSimdUnaryArithIx16 : public LSimdUnaryArith +{ + public: + LIR_HEADER(SimdUnaryArithIx16); + explicit LSimdUnaryArithIx16(const LAllocation& in) : LSimdUnaryArith(classOpcode, in) {} +}; + +// Unary SIMD arithmetic operation on a Int16x8 operand +class LSimdUnaryArithIx8 : public LSimdUnaryArith +{ + public: + LIR_HEADER(SimdUnaryArithIx8); + explicit LSimdUnaryArithIx8(const LAllocation& in) : LSimdUnaryArith(classOpcode, in) {} +}; + +// Unary SIMD arithmetic operation on a Int32x4 operand +class LSimdUnaryArithIx4 : public LSimdUnaryArith +{ + public: + LIR_HEADER(SimdUnaryArithIx4); + explicit LSimdUnaryArithIx4(const LAllocation& in) : LSimdUnaryArith(classOpcode, in) {} +}; + +// Unary SIMD arithmetic operation on a Float32x4 operand +class LSimdUnaryArithFx4 : public LSimdUnaryArith +{ + public: + LIR_HEADER(SimdUnaryArithFx4); + explicit LSimdUnaryArithFx4(const LAllocation& in) : LSimdUnaryArith(classOpcode, in) {} +}; + +// Binary SIMD bitwise operation between two 128-bit operands. +class LSimdBinaryBitwise : public LInstructionHelper<1, 2, 0> +{ + public: + LIR_HEADER(SimdBinaryBitwise); + LSimdBinaryBitwise() + : LInstructionHelper(classOpcode) + {} + const LAllocation* lhs() { + return getOperand(0); + } + const LAllocation* rhs() { + return getOperand(1); + } + MSimdBinaryBitwise::Operation operation() const { + return mir_->toSimdBinaryBitwise()->operation(); + } + const char* extraName() const { + return MSimdBinaryBitwise::OperationName(operation()); + } + MIRType type() const { + return mir_->type(); + } +}; + +// Shift a SIMD vector by a scalar amount. +// The temp register is only required if the shift amount is a dynamical +// value. If it is a constant, use a BogusTemp instead. +class LSimdShift : public LInstructionHelper<1, 2, 1> +{ + public: + LIR_HEADER(SimdShift) + LSimdShift(const LAllocation& vec, const LAllocation& val, const LDefinition& temp) + : LInstructionHelper(classOpcode) + { + setOperand(0, vec); + setOperand(1, val); + setTemp(0, temp); + } + const LAllocation* vector() { + return getOperand(0); + } + const LAllocation* value() { + return getOperand(1); + } + const LDefinition* temp() { + return getTemp(0); + } + MSimdShift::Operation operation() const { + return mir_->toSimdShift()->operation(); + } + const char* extraName() const { + return MSimdShift::OperationName(operation()); + } + MSimdShift* mir() const { + return mir_->toSimdShift(); + } + MIRType type() const { + return mir_->type(); + } +}; + +// SIMD selection of lanes from two int32x4 or float32x4 arguments based on a +// int32x4 argument. +class LSimdSelect : public LInstructionHelper<1, 3, 1> +{ + public: + LIR_HEADER(SimdSelect); + LSimdSelect() + : LInstructionHelper(classOpcode) + {} + const LAllocation* mask() { + return getOperand(0); + } + const LAllocation* lhs() { + return getOperand(1); + } + const LAllocation* rhs() { + return getOperand(2); + } + const LDefinition* temp() { + return getTemp(0); + } + MSimdSelect* mir() const { + return mir_->toSimdSelect(); + } +}; + +class LSimdAnyTrue : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdAnyTrue) + explicit LSimdAnyTrue(const LAllocation& input) + : LInstructionHelper(classOpcode) + { + setOperand(0, input); + } + const LAllocation* vector() { + return getOperand(0); + } + MSimdAnyTrue* mir() const { + return mir_->toSimdAnyTrue(); + } +}; + +class LSimdAllTrue : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(SimdAllTrue) + explicit LSimdAllTrue(const LAllocation& input) + : LInstructionHelper(classOpcode) + { + setOperand(0, input); + } + const LAllocation* vector() { + return getOperand(0); + } + MSimdAllTrue* mir() const { + return mir_->toSimdAllTrue(); + } +}; + + // Constant 32-bit integer. class LInteger : public LInstructionHelper<1, 0, 0> { @@ -275,6 +941,27 @@ class LFloat32 : public LInstructionHelper<1, 0, 0> } }; +// Constant 128-bit SIMD integer vector (8x16, 16x8, 32x4). +// Also used for Bool32x4, Bool16x8, etc. +class LSimd128Int : public LInstructionHelper<1, 0, 0> +{ + public: + LIR_HEADER(Simd128Int); + + explicit LSimd128Int() : LInstructionHelper(classOpcode) {} + const SimdConstant& getValue() const { return mir_->toSimdConstant()->value(); } +}; + +// Constant 128-bit SIMD floating point vector (32x4, 64x2). +class LSimd128Float : public LInstructionHelper<1, 0, 0> +{ + public: + LIR_HEADER(Simd128Float); + + explicit LSimd128Float() : LInstructionHelper(classOpcode) {} + const SimdConstant& getValue() const { return mir_->toSimdConstant()->value(); } +}; + // A constant Value. class LValue : public LInstructionHelper { @@ -4424,6 +5111,59 @@ class LValueToObjectOrNull : public LInstructionHelper<1, BOX_PIECES, 0> } }; +class LInt32x4ToFloat32x4 : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(Int32x4ToFloat32x4); + explicit LInt32x4ToFloat32x4(const LAllocation& input) + : LInstructionHelper(classOpcode) + { + setOperand(0, input); + } +}; + +class LFloat32x4ToInt32x4 : public LInstructionHelper<1, 1, 1> +{ + public: + LIR_HEADER(Float32x4ToInt32x4); + explicit LFloat32x4ToInt32x4(const LAllocation& input, const LDefinition& temp) + : LInstructionHelper(classOpcode) + { + setOperand(0, input); + setTemp(0, temp); + } + const LDefinition* temp() { + return getTemp(0); + } + const MSimdConvert* mir() const { + return mir_->toSimdConvert(); + } +}; + +// Float32x4 to Uint32x4 needs one GPR temp and one FloatReg temp. +class LFloat32x4ToUint32x4 : public LInstructionHelper<1, 1, 2> +{ + public: + LIR_HEADER(Float32x4ToUint32x4); + explicit LFloat32x4ToUint32x4(const LAllocation& input, const LDefinition& tempR, + const LDefinition& tempF) + : LInstructionHelper(classOpcode) + { + setOperand(0, input); + setTemp(0, tempR); + setTemp(1, tempF); + } + const LDefinition* tempR() { + return getTemp(0); + } + const LDefinition* tempF() { + return getTemp(1); + } + const MSimdConvert* mir() const { + return mir_->toSimdConvert(); + } +}; + // Double raised to a half power. class LPowHalfD : public LInstructionHelper<1, 1, 0> { diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index 03a684b963ca..acd553ba6ee7 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -374,6 +374,9 @@ IsCompatibleLIRCoercion(MIRType to, MIRType from) (from == MIRType::Int32 || from == MIRType::Boolean)) { return true; } + // SIMD types can be coerced with from*Bits operators. + if (IsSimdType(to) && IsSimdType(from)) + return true; return false; } diff --git a/js/src/jit/x64/Assembler-x64.h b/js/src/jit/x64/Assembler-x64.h index 31036fc26610..114dd2049879 100644 --- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -226,7 +226,7 @@ static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment > // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. -static constexpr bool SupportsSimd = false; +static constexpr bool SupportsSimd = true; static constexpr uint32_t SimdMemoryAlignment = 16; static_assert(CodeAlignment % SimdMemoryAlignment == 0, diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 4ad68d0a0b7c..5122053ad59c 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -387,6 +387,8 @@ CodeGeneratorX64::wasmStore(const wasm::MemoryAccessDesc& access, const LAllocat Operand dstAddr) { if (value->isConstant()) { + MOZ_ASSERT(!access.isSimd()); + masm.memoryBarrierBefore(access.sync()); const MConstant* mir = value->toConstant(); @@ -409,6 +411,10 @@ CodeGeneratorX64::wasmStore(const wasm::MemoryAccessDesc& access, const LAllocat case Scalar::Int64: case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -493,6 +499,7 @@ CodeGenerator::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins) const LDefinition* out = ins->output(); Scalar::Type accessType = mir->access().type(); + MOZ_ASSERT(!Scalar::isSimdType(accessType)); Operand srcAddr = ptr->isBogus() ? Operand(HeapReg, mir->offset()) @@ -514,6 +521,7 @@ CodeGenerator::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins) const LAllocation* value = ins->value(); Scalar::Type accessType = mir->access().type(); + MOZ_ASSERT(!Scalar::isSimdType(accessType)); canonicalizeIfDeterministic(accessType, value); diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp index 22e5f1d72361..fa084bbc4a76 100644 --- a/js/src/jit/x64/Lowering-x64.cpp +++ b/js/src/jit/x64/Lowering-x64.cpp @@ -236,6 +236,10 @@ LIRGenerator::visitWasmStore(MWasmStore* ins) break; case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: valueAlloc = useRegisterAtStart(value); break; case Scalar::Uint8Clamped: @@ -276,6 +280,10 @@ LIRGenerator::visitAsmJSStoreHeap(MAsmJSStoreHeap* ins) break; case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: lir = new(alloc()) LAsmJSStoreHeap(useRegisterOrZeroAtStart(base), useRegisterAtStart(ins->value())); break; diff --git a/js/src/jit/x64/MacroAssembler-x64.cpp b/js/src/jit/x64/MacroAssembler-x64.cpp index 698b18e5d1a0..39ee69798590 100644 --- a/js/src/jit/x64/MacroAssembler-x64.cpp +++ b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -618,6 +618,34 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, case Scalar::Float64: loadDouble(srcAddr, out.fpu()); break; + case Scalar::Float32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movss zeroes out the high lanes. + case 1: loadFloat32(srcAddr, out.fpu()); break; + // See comment above, which also applies to movsd. + case 2: loadDouble(srcAddr, out.fpu()); break; + case 4: loadUnalignedSimd128Float(srcAddr, out.fpu()); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movd zeroes out the high lanes. + case 1: vmovd(srcAddr, out.fpu()); break; + // See comment above, which also applies to movq. + case 2: vmovq(srcAddr, out.fpu()); break; + case 4: loadUnalignedSimd128Int(srcAddr, out.fpu()); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial load"); + loadUnalignedSimd128Int(srcAddr, out.fpu()); + break; + case Scalar::Int16x8: + MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial load"); + loadUnalignedSimd128Int(srcAddr, out.fpu()); + break; case Scalar::Int64: MOZ_CRASH("int64 loads must use load64"); case Scalar::Uint8Clamped: @@ -633,6 +661,8 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd { memoryBarrierBefore(access.sync()); + MOZ_ASSERT(!access.isSimd()); + append(access, size()); switch (access.type()) { case Scalar::Int8: @@ -659,6 +689,10 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd break; case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: @@ -696,6 +730,34 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu case Scalar::Float64: storeUncanonicalizedDouble(value.fpu(), dstAddr); break; + case Scalar::Float32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movss zeroes out the high lanes. + case 1: storeUncanonicalizedFloat32(value.fpu(), dstAddr); break; + // See comment above, which also applies to movsd. + case 2: storeUncanonicalizedDouble(value.fpu(), dstAddr); break; + case 4: storeUnalignedSimd128Float(value.fpu(), dstAddr); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movd zeroes out the high lanes. + case 1: vmovd(value.fpu(), dstAddr); break; + // See comment above, which also applies to movq. + case 2: vmovq(value.fpu(), dstAddr); break; + case 4: storeUnalignedSimd128Int(value.fpu(), dstAddr); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial store"); + storeUnalignedSimd128Int(value.fpu(), dstAddr); + break; + case Scalar::Int16x8: + MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial store"); + storeUnalignedSimd128Int(value.fpu(), dstAddr); + break; case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 3261c72c7099..a51cb6d31cdc 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -29,7 +29,6 @@ using mozilla::BitwiseCast; using mozilla::DebugOnly; using mozilla::FloatingPoint; using mozilla::FloorLog2; -using mozilla::Maybe; using mozilla::NegativeInfinity; using mozilla::SpecificNaN; @@ -301,11 +300,16 @@ CodeGenerator::visitWasmStackArg(LWasmStackArg* ins) case MIRType::Float32: masm.storeFloat32(ToFloatRegister(ins->arg()), dst); return; + // StackPointer is SIMD-aligned and ABIArgGenerator guarantees + // stack offsets are SIMD-aligned. case MIRType::Int32x4: case MIRType::Bool32x4: + masm.storeAlignedSimd128Int(ToFloatRegister(ins->arg()), dst); + return; case MIRType::Float32x4: - default: - break; + masm.storeAlignedSimd128Float(ToFloatRegister(ins->arg()), dst); + return; + default: break; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected mir type in WasmStackArg"); } @@ -395,6 +399,10 @@ CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTyp { switch (ool->viewType()) { case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); case Scalar::Float32: @@ -2479,6 +2487,1776 @@ CodeGenerator::visitNegF(LNegF* ins) masm.negateFloat(input); } +void +CodeGenerator::visitSimd128Int(LSimd128Int* ins) +{ + const LDefinition* out = ins->getDef(0); + masm.loadConstantSimd128Int(ins->getValue(), ToFloatRegister(out)); +} + +void +CodeGenerator::visitSimd128Float(LSimd128Float* ins) +{ + const LDefinition* out = ins->getDef(0); + masm.loadConstantSimd128Float(ins->getValue(), ToFloatRegister(out)); +} + +void +CodeGenerator::visitInt32x4ToFloat32x4(LInt32x4ToFloat32x4* ins) +{ + FloatRegister in = ToFloatRegister(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + masm.convertInt32x4ToFloat32x4(in, out); +} + +void +CodeGenerator::visitFloat32x4ToInt32x4(LFloat32x4ToInt32x4* ins) +{ + FloatRegister in = ToFloatRegister(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + Register temp = ToRegister(ins->temp()); + + masm.convertFloat32x4ToInt32x4(in, out); + + auto* ool = new(alloc()) OutOfLineSimdFloatToIntCheck(temp, in, ins, + ins->mir()->bytecodeOffset()); + addOutOfLineCode(ool, ins->mir()); + + static const SimdConstant InvalidResult = SimdConstant::SplatX4(int32_t(-2147483648)); + + ScratchSimd128Scope scratch(masm); + masm.loadConstantSimd128Int(InvalidResult, scratch); + masm.packedEqualInt32x4(Operand(out), scratch); + // TODO (bug 1156228): If we have SSE4.1, we can use PTEST here instead of + // the two following instructions. + masm.vmovmskps(scratch, temp); + masm.cmp32(temp, Imm32(0)); + masm.j(Assembler::NotEqual, ool->entry()); + + masm.bind(ool->rejoin()); +} + +void +CodeGeneratorX86Shared::visitOutOfLineSimdFloatToIntCheck(OutOfLineSimdFloatToIntCheck *ool) +{ + static const SimdConstant Int32MaxX4 = SimdConstant::SplatX4(2147483647.f); + static const SimdConstant Int32MinX4 = SimdConstant::SplatX4(-2147483648.f); + + Label onConversionError; + + FloatRegister input = ool->input(); + Register temp = ool->temp(); + + ScratchSimd128Scope scratch(masm); + masm.loadConstantSimd128Float(Int32MinX4, scratch); + masm.vcmpleps(Operand(input), scratch, scratch); + masm.vmovmskps(scratch, temp); + masm.cmp32(temp, Imm32(15)); + masm.j(Assembler::NotEqual, &onConversionError); + + masm.loadConstantSimd128Float(Int32MaxX4, scratch); + masm.vcmpleps(Operand(input), scratch, scratch); + masm.vmovmskps(scratch, temp); + masm.cmp32(temp, Imm32(0)); + masm.j(Assembler::NotEqual, &onConversionError); + + masm.jump(ool->rejoin()); + + masm.bind(&onConversionError); + if (gen->compilingWasm()) + masm.wasmTrap(wasm::Trap::ImpreciseSimdConversion, ool->bytecodeOffset()); + else + bailout(ool->ins()->snapshot()); +} + +// Convert Float32x4 to Uint32x4. +// +// If any input lane value is out of range or NaN, bail out. +void +CodeGenerator::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4* ins) +{ + const MSimdConvert* mir = ins->mir(); + FloatRegister in = ToFloatRegister(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + Register temp = ToRegister(ins->tempR()); + FloatRegister tempF = ToFloatRegister(ins->tempF()); + + // Classify lane values into 4 disjoint classes: + // + // N-lanes: in <= -1.0 + // A-lanes: -1.0 < in <= 0x0.ffffffp31 + // B-lanes: 0x1.0p31 <= in <= 0x0.ffffffp32 + // V-lanes: 0x1.0p32 <= in, or isnan(in) + // + // We need to bail out to throw a RangeError if we see any N-lanes or + // V-lanes. + // + // For A-lanes and B-lanes, we make two float -> int32 conversions: + // + // A = cvttps2dq(in) + // B = cvttps2dq(in - 0x1.0p31f) + // + // Note that the subtraction for the B computation is exact for B-lanes. + // There is no rounding, so B is the low 31 bits of the correctly converted + // result. + // + // The cvttps2dq instruction produces 0x80000000 when the input is NaN or + // out of range for a signed int32_t. This conveniently provides the missing + // high bit for B, so the desired result is A for A-lanes and A|B for + // B-lanes. + + ScratchSimd128Scope scratch(masm); + + // TODO: If the majority of lanes are A-lanes, it could be faster to compute + // A first, use vmovmskps to check for any non-A-lanes and handle them in + // ool code. OTOH, we we're wrong about the lane distribution, that would be + // slower. + + // Compute B in |scratch|. + static const float Adjust = 0x80000000; // 0x1.0p31f for the benefit of MSVC. + static const SimdConstant Bias = SimdConstant::SplatX4(-Adjust); + masm.loadConstantSimd128Float(Bias, scratch); + masm.packedAddFloat32(Operand(in), scratch); + masm.convertFloat32x4ToInt32x4(scratch, scratch); + + // Compute A in |out|. This is the last time we use |in| and the first time + // we use |out|, so we can tolerate if they are the same register. + masm.convertFloat32x4ToInt32x4(in, out); + + // We can identify A-lanes by the sign bits in A: Any A-lanes will be + // positive in A, and N, B, and V-lanes will be 0x80000000 in A. Compute a + // mask of non-A-lanes into |tempF|. + masm.zeroSimd128Float(tempF); + masm.packedGreaterThanInt32x4(Operand(out), tempF); + + // Clear the A-lanes in B. + masm.bitwiseAndSimd128(Operand(tempF), scratch); + + // Compute the final result: A for A-lanes, A|B for B-lanes. + masm.bitwiseOrSimd128(Operand(scratch), out); + + // We still need to filter out the V-lanes. They would show up as 0x80000000 + // in both A and B. Since we cleared the valid A-lanes in B, the V-lanes are + // the remaining negative lanes in B. + masm.vmovmskps(scratch, temp); + masm.cmp32(temp, Imm32(0)); + + if (gen->compilingWasm()) { + Label ok; + masm.j(Assembler::Equal, &ok); + masm.wasmTrap(wasm::Trap::ImpreciseSimdConversion, mir->bytecodeOffset()); + masm.bind(&ok); + } else { + bailoutIf(Assembler::NotEqual, ins->snapshot()); + } +} + +void +CodeGenerator::visitSimdValueInt32x4(LSimdValueInt32x4* ins) +{ + MOZ_ASSERT(ins->mir()->type() == MIRType::Int32x4 || ins->mir()->type() == MIRType::Bool32x4); + + FloatRegister output = ToFloatRegister(ins->output()); + if (AssemblerX86Shared::HasSSE41()) { + masm.vmovd(ToRegister(ins->getOperand(0)), output); + for (size_t i = 1; i < 4; ++i) { + Register r = ToRegister(ins->getOperand(i)); + masm.vpinsrd(i, r, output, output); + } + return; + } + + masm.reserveStack(Simd128DataSize); + for (size_t i = 0; i < 4; ++i) { + Register r = ToRegister(ins->getOperand(i)); + masm.store32(r, Address(StackPointer, i * sizeof(int32_t))); + } + masm.loadAlignedSimd128Int(Address(StackPointer, 0), output); + masm.freeStack(Simd128DataSize); +} + +void +CodeGenerator::visitSimdValueFloat32x4(LSimdValueFloat32x4* ins) +{ + MOZ_ASSERT(ins->mir()->type() == MIRType::Float32x4); + + FloatRegister r0 = ToFloatRegister(ins->getOperand(0)); + FloatRegister r1 = ToFloatRegister(ins->getOperand(1)); + FloatRegister r2 = ToFloatRegister(ins->getOperand(2)); + FloatRegister r3 = ToFloatRegister(ins->getOperand(3)); + FloatRegister tmp = ToFloatRegister(ins->getTemp(0)); + FloatRegister output = ToFloatRegister(ins->output()); + + FloatRegister r0Copy = masm.reusedInputFloat32x4(r0, output); + FloatRegister r1Copy = masm.reusedInputFloat32x4(r1, tmp); + + masm.vunpcklps(r3, r1Copy, tmp); + masm.vunpcklps(r2, r0Copy, output); + masm.vunpcklps(tmp, output, output); +} + +void +CodeGenerator::visitSimdSplatX16(LSimdSplatX16* ins) +{ + MOZ_ASSERT(SimdTypeToLength(ins->mir()->type()) == 16); + Register input = ToRegister(ins->getOperand(0)); + FloatRegister output = ToFloatRegister(ins->output()); + masm.vmovd(input, output); + if (AssemblerX86Shared::HasSSSE3()) { + masm.zeroSimd128Int(ScratchSimd128Reg); + masm.vpshufb(ScratchSimd128Reg, output, output); + } else { + // Use two shifts to duplicate the low 8 bits into the low 16 bits. + masm.vpsllw(Imm32(8), output, output); + masm.vmovdqa(output, ScratchSimd128Reg); + masm.vpsrlw(Imm32(8), ScratchSimd128Reg, ScratchSimd128Reg); + masm.vpor(ScratchSimd128Reg, output, output); + // Then do an X8 splat. + masm.vpshuflw(0, output, output); + masm.vpshufd(0, output, output); + } +} + +void +CodeGenerator::visitSimdSplatX8(LSimdSplatX8* ins) +{ + MOZ_ASSERT(SimdTypeToLength(ins->mir()->type()) == 8); + Register input = ToRegister(ins->getOperand(0)); + FloatRegister output = ToFloatRegister(ins->output()); + masm.vmovd(input, output); + masm.vpshuflw(0, output, output); + masm.vpshufd(0, output, output); +} + +void +CodeGenerator::visitSimdSplatX4(LSimdSplatX4* ins) +{ + FloatRegister output = ToFloatRegister(ins->output()); + + MSimdSplat* mir = ins->mir(); + MOZ_ASSERT(IsSimdType(mir->type())); + JS_STATIC_ASSERT(sizeof(float) == sizeof(int32_t)); + + if (mir->type() == MIRType::Float32x4) { + FloatRegister r = ToFloatRegister(ins->getOperand(0)); + FloatRegister rCopy = masm.reusedInputFloat32x4(r, output); + masm.vshufps(0, rCopy, rCopy, output); + } else { + Register r = ToRegister(ins->getOperand(0)); + masm.vmovd(r, output); + masm.vpshufd(0, output, output); + } +} + +void +CodeGenerator::visitSimdReinterpretCast(LSimdReinterpretCast* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + + if (input.aliases(output)) + return; + + if (IsIntegerSimdType(ins->mir()->type())) + masm.vmovdqa(input, output); + else + masm.vmovaps(input, output); +} + +// Extract an integer lane from the 32x4 vector register |input| and place it in +// |output|. +void +CodeGeneratorX86Shared::emitSimdExtractLane32x4(FloatRegister input, Register output, unsigned lane) +{ + if (lane == 0) { + // The value we want to extract is in the low double-word + masm.moveLowInt32(input, output); + } else if (AssemblerX86Shared::HasSSE41()) { + masm.vpextrd(lane, input, output); + } else { + uint32_t mask = MacroAssembler::ComputeShuffleMask(lane); + masm.shuffleInt32(mask, input, ScratchSimd128Reg); + masm.moveLowInt32(ScratchSimd128Reg, output); + } +} + +// Extract an integer lane from the 16x8 vector register |input|, sign- or +// zero-extend to 32 bits and place the result in |output|. +void +CodeGeneratorX86Shared::emitSimdExtractLane16x8(FloatRegister input, Register output, + unsigned lane, SimdSign signedness) +{ + // Unlike pextrd and pextrb, this is available in SSE2. + masm.vpextrw(lane, input, output); + + if (signedness == SimdSign::Signed) + masm.movswl(output, output); +} + +// Extract an integer lane from the 8x16 vector register |input|, sign- or +// zero-extend to 32 bits and place the result in |output|. +void +CodeGeneratorX86Shared::emitSimdExtractLane8x16(FloatRegister input, Register output, + unsigned lane, SimdSign signedness) +{ + if (AssemblerX86Shared::HasSSE41()) { + masm.vpextrb(lane, input, output); + // vpextrb clears the high bits, so no further extension required. + if (signedness == SimdSign::Unsigned) + signedness = SimdSign::NotApplicable; + } else { + // Extract the relevant 16 bits containing our lane, then shift the + // right 8 bits into place. + emitSimdExtractLane16x8(input, output, lane / 2, SimdSign::Unsigned); + if (lane % 2) { + masm.shrl(Imm32(8), output); + // The shrl handles the zero-extension. Don't repeat it. + if (signedness == SimdSign::Unsigned) + signedness = SimdSign::NotApplicable; + } + } + + // We have the right low 8 bits in |output|, but we may need to fix the high + // bits. Note that this requires |output| to be one of the %eax-%edx + // registers. + switch (signedness) { + case SimdSign::Signed: + masm.movsbl(output, output); + break; + case SimdSign::Unsigned: + masm.movzbl(output, output); + break; + case SimdSign::NotApplicable: + // No adjustment needed. + break; + } +} + +void +CodeGenerator::visitSimdExtractElementB(LSimdExtractElementB* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + Register output = ToRegister(ins->output()); + MSimdExtractElement* mir = ins->mir(); + unsigned length = SimdTypeToLength(mir->specialization()); + + switch (length) { + case 4: + emitSimdExtractLane32x4(input, output, mir->lane()); + break; + case 8: + // Get a lane, don't bother fixing the high bits since we'll mask below. + emitSimdExtractLane16x8(input, output, mir->lane(), SimdSign::NotApplicable); + break; + case 16: + emitSimdExtractLane8x16(input, output, mir->lane(), SimdSign::NotApplicable); + break; + default: + MOZ_CRASH("Unhandled SIMD length"); + } + + // We need to generate a 0/1 value. We have 0/-1 and possibly dirty high bits. + masm.and32(Imm32(1), output); +} + +void +CodeGenerator::visitSimdExtractElementI(LSimdExtractElementI* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + Register output = ToRegister(ins->output()); + MSimdExtractElement* mir = ins->mir(); + unsigned length = SimdTypeToLength(mir->specialization()); + + switch (length) { + case 4: + emitSimdExtractLane32x4(input, output, mir->lane()); + break; + case 8: + emitSimdExtractLane16x8(input, output, mir->lane(), mir->signedness()); + break; + case 16: + emitSimdExtractLane8x16(input, output, mir->lane(), mir->signedness()); + break; + default: + MOZ_CRASH("Unhandled SIMD length"); + } +} + +void +CodeGenerator::visitSimdExtractElementU2D(LSimdExtractElementU2D* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + Register temp = ToRegister(ins->temp()); + MSimdExtractElement* mir = ins->mir(); + MOZ_ASSERT(mir->specialization() == MIRType::Int32x4); + emitSimdExtractLane32x4(input, temp, mir->lane()); + masm.convertUInt32ToDouble(temp, output); +} + +void +CodeGenerator::visitSimdExtractElementF(LSimdExtractElementF* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + + unsigned lane = ins->mir()->lane(); + if (lane == 0) { + // The value we want to extract is in the low double-word + if (input != output) + masm.moveFloat32(input, output); + } else if (lane == 2) { + masm.moveHighPairToLowPairFloat32(input, output); + } else { + uint32_t mask = MacroAssembler::ComputeShuffleMask(lane); + masm.shuffleFloat32(mask, input, output); + } + // NaNs contained within SIMD values are not enforced to be canonical, so + // when we extract an element into a "regular" scalar JS value, we have to + // canonicalize. In wasm code, we can skip this, as wasm only has to + // canonicalize NaNs at FFI boundaries. + if (!gen->compilingWasm()) + masm.canonicalizeFloat(output); +} + +void +CodeGenerator::visitSimdInsertElementI(LSimdInsertElementI* ins) +{ + FloatRegister vector = ToFloatRegister(ins->vector()); + Register value = ToRegister(ins->value()); + FloatRegister output = ToFloatRegister(ins->output()); + MOZ_ASSERT(vector == output); // defineReuseInput(0) + + unsigned lane = ins->lane(); + unsigned length = ins->length(); + + if (length == 8) { + // Available in SSE 2. + masm.vpinsrw(lane, value, vector, output); + return; + } + + // Note that, contrarily to float32x4, we cannot use vmovd if the inserted + // value goes into the first component, as vmovd clears out the higher lanes + // of the output. + if (AssemblerX86Shared::HasSSE41()) { + // TODO: Teach Lowering that we don't need defineReuseInput if we have AVX. + switch (length) { + case 4: + masm.vpinsrd(lane, value, vector, output); + return; + case 16: + masm.vpinsrb(lane, value, vector, output); + return; + } + } + + masm.reserveStack(Simd128DataSize); + masm.storeAlignedSimd128Int(vector, Address(StackPointer, 0)); + switch (length) { + case 4: + masm.store32(value, Address(StackPointer, lane * sizeof(int32_t))); + break; + case 16: + // Note that this requires `value` to be in one the registers where the + // low 8 bits are addressible (%eax - %edx on x86, all of them on x86-64). + masm.store8(value, Address(StackPointer, lane * sizeof(int8_t))); + break; + default: + MOZ_CRASH("Unsupported SIMD length"); + } + masm.loadAlignedSimd128Int(Address(StackPointer, 0), output); + masm.freeStack(Simd128DataSize); +} + +void +CodeGenerator::visitSimdInsertElementF(LSimdInsertElementF* ins) +{ + FloatRegister vector = ToFloatRegister(ins->vector()); + FloatRegister value = ToFloatRegister(ins->value()); + FloatRegister output = ToFloatRegister(ins->output()); + MOZ_ASSERT(vector == output); // defineReuseInput(0) + + if (ins->lane() == 0) { + // As both operands are registers, vmovss doesn't modify the upper bits + // of the destination operand. + if (value != output) + masm.vmovss(value, vector, output); + return; + } + + if (AssemblerX86Shared::HasSSE41()) { + // The input value is in the low float32 of the 'value' FloatRegister. + masm.vinsertps(masm.vinsertpsMask(0, ins->lane()), value, output, output); + return; + } + + unsigned component = unsigned(ins->lane()); + masm.reserveStack(Simd128DataSize); + masm.storeAlignedSimd128Float(vector, Address(StackPointer, 0)); + masm.storeFloat32(value, Address(StackPointer, component * sizeof(int32_t))); + masm.loadAlignedSimd128Float(Address(StackPointer, 0), output); + masm.freeStack(Simd128DataSize); +} + +void +CodeGenerator::visitSimdAllTrue(LSimdAllTrue* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + Register output = ToRegister(ins->output()); + + // We know that the input lanes are boolean, so they are either 0 or -1. + // The all-true vector has all 128 bits set, no matter the lane geometry. + masm.vpmovmskb(input, output); + masm.cmp32(output, Imm32(0xffff)); + masm.emitSet(Assembler::Zero, output); +} + +void +CodeGenerator::visitSimdAnyTrue(LSimdAnyTrue* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + Register output = ToRegister(ins->output()); + + masm.vpmovmskb(input, output); + masm.cmp32(output, Imm32(0x0)); + masm.emitSet(Assembler::NonZero, output); +} + +template void +CodeGeneratorX86Shared::visitSimdGeneralShuffle(LSimdGeneralShuffleBase* ins, Reg tempRegister) +{ + MSimdGeneralShuffle* mir = ins->mir(); + unsigned numVectors = mir->numVectors(); + + Register laneTemp = ToRegister(ins->temp()); + + // This won't generate fast code, but it's fine because we expect users + // to have used constant indices (and thus MSimdGeneralShuffle to be fold + // into MSimdSwizzle/MSimdShuffle, which are fast). + + // We need stack space for the numVectors inputs and for the output vector. + unsigned stackSpace = Simd128DataSize * (numVectors + 1); + masm.reserveStack(stackSpace); + + for (unsigned i = 0; i < numVectors; i++) { + masm.storeAlignedVector(ToFloatRegister(ins->vector(i)), + Address(StackPointer, Simd128DataSize * (1 + i))); + } + + Label bail; + const Scale laneScale = ScaleFromElemWidth(sizeof(T)); + + for (size_t i = 0; i < mir->numLanes(); i++) { + Operand lane = ToOperand(ins->lane(i)); + + masm.cmp32(lane, Imm32(numVectors * mir->numLanes() - 1)); + masm.j(Assembler::Above, &bail); + + if (lane.kind() == Operand::REG) { + masm.loadScalar(Operand(StackPointer, ToRegister(ins->lane(i)), laneScale, Simd128DataSize), + tempRegister); + } else { + masm.load32(lane, laneTemp); + masm.loadScalar(Operand(StackPointer, laneTemp, laneScale, Simd128DataSize), tempRegister); + } + + masm.storeScalar(tempRegister, Address(StackPointer, i * sizeof(T))); + } + + FloatRegister output = ToFloatRegister(ins->output()); + masm.loadAlignedVector(Address(StackPointer, 0), output); + + Label join; + masm.jump(&join); + + { + masm.bind(&bail); + masm.freeStack(stackSpace); + bailout(ins->snapshot()); + } + + masm.bind(&join); + masm.setFramePushed(masm.framePushed() + stackSpace); + masm.freeStack(stackSpace); +} + +void +CodeGenerator::visitSimdGeneralShuffleI(LSimdGeneralShuffleI* ins) +{ + switch (ins->mir()->type()) { + case MIRType::Int8x16: + return visitSimdGeneralShuffle(ins, ToRegister(ins->temp())); + case MIRType::Int16x8: + return visitSimdGeneralShuffle(ins, ToRegister(ins->temp())); + case MIRType::Int32x4: + return visitSimdGeneralShuffle(ins, ToRegister(ins->temp())); + default: + MOZ_CRASH("unsupported type for general shuffle"); + } +} +void +CodeGenerator::visitSimdGeneralShuffleF(LSimdGeneralShuffleF* ins) +{ + ScratchFloat32Scope scratch(masm); + visitSimdGeneralShuffle(ins, scratch); +} + +void +CodeGenerator::visitSimdSwizzleI(LSimdSwizzleI* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + const unsigned numLanes = ins->numLanes(); + + switch (numLanes) { + case 4: { + uint32_t x = ins->lane(0); + uint32_t y = ins->lane(1); + uint32_t z = ins->lane(2); + uint32_t w = ins->lane(3); + + uint32_t mask = MacroAssembler::ComputeShuffleMask(x, y, z, w); + masm.shuffleInt32(mask, input, output); + return; + } + } + + // In the general case, use pshufb if it is available. Convert to a + // byte-wise swizzle. + const unsigned bytesPerLane = 16 / numLanes; + int8_t bLane[16]; + for (unsigned i = 0; i < numLanes; i++) { + for (unsigned b = 0; b < bytesPerLane; b++) { + bLane[i * bytesPerLane + b] = ins->lane(i) * bytesPerLane + b; + } + } + + if (AssemblerX86Shared::HasSSSE3()) { + ScratchSimd128Scope scratch(masm); + masm.loadConstantSimd128Int(SimdConstant::CreateX16(bLane), scratch); + FloatRegister inputCopy = masm.reusedInputInt32x4(input, output); + masm.vpshufb(scratch, inputCopy, output); + return; + } + + // Worst-case fallback for pre-SSSE3 machines. Bounce through memory. + Register temp = ToRegister(ins->getTemp(0)); + masm.reserveStack(2 * Simd128DataSize); + masm.storeAlignedSimd128Int(input, Address(StackPointer, Simd128DataSize)); + for (unsigned i = 0; i < 16; i++) { + masm.load8ZeroExtend(Address(StackPointer, Simd128DataSize + bLane[i]), temp); + masm.store8(temp, Address(StackPointer, i)); + } + masm.loadAlignedSimd128Int(Address(StackPointer, 0), output); + masm.freeStack(2 * Simd128DataSize); +} + +void +CodeGenerator::visitSimdSwizzleF(LSimdSwizzleF* ins) +{ + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + MOZ_ASSERT(ins->numLanes() == 4); + + uint32_t x = ins->lane(0); + uint32_t y = ins->lane(1); + uint32_t z = ins->lane(2); + uint32_t w = ins->lane(3); + + if (AssemblerX86Shared::HasSSE3()) { + if (ins->lanesMatch(0, 0, 2, 2)) { + masm.vmovsldup(input, output); + return; + } + if (ins->lanesMatch(1, 1, 3, 3)) { + masm.vmovshdup(input, output); + return; + } + } + + // TODO Here and below, arch specific lowering could identify this pattern + // and use defineReuseInput to avoid this move (bug 1084404) + if (ins->lanesMatch(2, 3, 2, 3)) { + FloatRegister inputCopy = masm.reusedInputFloat32x4(input, output); + masm.vmovhlps(input, inputCopy, output); + return; + } + + if (ins->lanesMatch(0, 1, 0, 1)) { + if (AssemblerX86Shared::HasSSE3() && !AssemblerX86Shared::HasAVX()) { + masm.vmovddup(input, output); + return; + } + FloatRegister inputCopy = masm.reusedInputFloat32x4(input, output); + masm.vmovlhps(input, inputCopy, output); + return; + } + + if (ins->lanesMatch(0, 0, 1, 1)) { + FloatRegister inputCopy = masm.reusedInputFloat32x4(input, output); + masm.vunpcklps(input, inputCopy, output); + return; + } + + if (ins->lanesMatch(2, 2, 3, 3)) { + FloatRegister inputCopy = masm.reusedInputFloat32x4(input, output); + masm.vunpckhps(input, inputCopy, output); + return; + } + + uint32_t mask = MacroAssembler::ComputeShuffleMask(x, y, z, w); + masm.shuffleFloat32(mask, input, output); +} + +void +CodeGenerator::visitSimdShuffle(LSimdShuffle* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + FloatRegister rhs = ToFloatRegister(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + const unsigned numLanes = ins->numLanes(); + const unsigned bytesPerLane = 16 / numLanes; + + // Convert the shuffle to a byte-wise shuffle. + uint8_t bLane[16]; + for (unsigned i = 0; i < numLanes; i++) { + for (unsigned b = 0; b < bytesPerLane; b++) { + bLane[i * bytesPerLane + b] = ins->lane(i) * bytesPerLane + b; + } + } + + // Use pshufb if it is available. + if (AssemblerX86Shared::HasSSSE3()) { + FloatRegister scratch1 = ToFloatRegister(ins->temp()); + ScratchSimd128Scope scratch2(masm); + + // Use pshufb instructions to gather the lanes from each source vector. + // A negative index creates a zero lane, so the two vectors can be combined. + + // Set scratch2 = lanes from lhs. + int8_t idx[16]; + for (unsigned i = 0; i < 16; i++) + idx[i] = bLane[i] < 16 ? bLane[i] : -1; + masm.loadConstantSimd128Int(SimdConstant::CreateX16(idx), scratch1); + FloatRegister lhsCopy = masm.reusedInputInt32x4(lhs, scratch2); + masm.vpshufb(scratch1, lhsCopy, scratch2); + + // Set output = lanes from rhs. + for (unsigned i = 0; i < 16; i++) + idx[i] = bLane[i] >= 16 ? bLane[i] - 16 : -1; + masm.loadConstantSimd128Int(SimdConstant::CreateX16(idx), scratch1); + FloatRegister rhsCopy = masm.reusedInputInt32x4(rhs, output); + masm.vpshufb(scratch1, rhsCopy, output); + + // Combine. + masm.vpor(scratch2, output, output); + return; + } + + // Worst-case fallback for pre-SSE3 machines. Bounce through memory. + Register temp = ToRegister(ins->getTemp(0)); + masm.reserveStack(3 * Simd128DataSize); + masm.storeAlignedSimd128Int(lhs, Address(StackPointer, Simd128DataSize)); + masm.storeAlignedSimd128Int(rhs, Address(StackPointer, 2 * Simd128DataSize)); + for (unsigned i = 0; i < 16; i++) { + masm.load8ZeroExtend(Address(StackPointer, Simd128DataSize + bLane[i]), temp); + masm.store8(temp, Address(StackPointer, i)); + } + masm.loadAlignedSimd128Int(Address(StackPointer, 0), output); + masm.freeStack(3 * Simd128DataSize); +} + +void +CodeGenerator::visitSimdShuffleX4(LSimdShuffleX4* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister out = ToFloatRegister(ins->output()); + + uint32_t x = ins->lane(0); + uint32_t y = ins->lane(1); + uint32_t z = ins->lane(2); + uint32_t w = ins->lane(3); + + // Check that lanes come from LHS in majority: + unsigned numLanesFromLHS = (x < 4) + (y < 4) + (z < 4) + (w < 4); + MOZ_ASSERT(numLanesFromLHS >= 2); + + // When reading this method, remember that vshufps takes the two first + // inputs of the destination operand (right operand) and the two last + // inputs of the source operand (left operand). + // + // Legend for explanations: + // - L: LHS + // - R: RHS + // - T: temporary + + uint32_t mask; + + // If all lanes came from a single vector, we should have constructed a + // MSimdSwizzle instead. + MOZ_ASSERT(numLanesFromLHS < 4); + + // If all values stay in their lane, this is a blend. + if (AssemblerX86Shared::HasSSE41()) { + if (x % 4 == 0 && y % 4 == 1 && z % 4 == 2 && w % 4 == 3) { + masm.vblendps(masm.blendpsMask(x >= 4, y >= 4, z >= 4, w >= 4), rhs, lhs, out); + return; + } + } + + // One element of the second, all other elements of the first + if (numLanesFromLHS == 3) { + unsigned firstMask = -1, secondMask = -1; + + // register-register vmovss preserves the high lanes. + if (ins->lanesMatch(4, 1, 2, 3) && rhs.kind() == Operand::FPREG) { + masm.vmovss(FloatRegister::FromCode(rhs.fpu()), lhs, out); + return; + } + + // SSE4.1 vinsertps can handle any single element. + unsigned numLanesUnchanged = (x == 0) + (y == 1) + (z == 2) + (w == 3); + if (AssemblerX86Shared::HasSSE41() && numLanesUnchanged == 3) { + unsigned srcLane; + unsigned dstLane; + if (x >= 4) { + srcLane = x - 4; + dstLane = 0; + } else if (y >= 4) { + srcLane = y - 4; + dstLane = 1; + } else if (z >= 4) { + srcLane = z - 4; + dstLane = 2; + } else { + MOZ_ASSERT(w >= 4); + srcLane = w - 4; + dstLane = 3; + } + masm.vinsertps(masm.vinsertpsMask(srcLane, dstLane), rhs, lhs, out); + return; + } + + FloatRegister rhsCopy = ToFloatRegister(ins->temp()); + + if (x < 4 && y < 4) { + if (w >= 4) { + w %= 4; + // T = (Rw Rw Lz Lz) = vshufps(firstMask, lhs, rhs, rhsCopy) + firstMask = MacroAssembler::ComputeShuffleMask(w, w, z, z); + // (Lx Ly Lz Rw) = (Lx Ly Tz Tx) = vshufps(secondMask, T, lhs, out) + secondMask = MacroAssembler::ComputeShuffleMask(x, y, 2, 0); + } else { + MOZ_ASSERT(z >= 4); + z %= 4; + // T = (Rz Rz Lw Lw) = vshufps(firstMask, lhs, rhs, rhsCopy) + firstMask = MacroAssembler::ComputeShuffleMask(z, z, w, w); + // (Lx Ly Rz Lw) = (Lx Ly Tx Tz) = vshufps(secondMask, T, lhs, out) + secondMask = MacroAssembler::ComputeShuffleMask(x, y, 0, 2); + } + + masm.vshufps(firstMask, lhs, rhsCopy, rhsCopy); + masm.vshufps(secondMask, rhsCopy, lhs, out); + return; + } + + MOZ_ASSERT(z < 4 && w < 4); + + if (y >= 4) { + y %= 4; + // T = (Ry Ry Lx Lx) = vshufps(firstMask, lhs, rhs, rhsCopy) + firstMask = MacroAssembler::ComputeShuffleMask(y, y, x, x); + // (Lx Ry Lz Lw) = (Tz Tx Lz Lw) = vshufps(secondMask, lhs, T, out) + secondMask = MacroAssembler::ComputeShuffleMask(2, 0, z, w); + } else { + MOZ_ASSERT(x >= 4); + x %= 4; + // T = (Rx Rx Ly Ly) = vshufps(firstMask, lhs, rhs, rhsCopy) + firstMask = MacroAssembler::ComputeShuffleMask(x, x, y, y); + // (Rx Ly Lz Lw) = (Tx Tz Lz Lw) = vshufps(secondMask, lhs, T, out) + secondMask = MacroAssembler::ComputeShuffleMask(0, 2, z, w); + } + + masm.vshufps(firstMask, lhs, rhsCopy, rhsCopy); + if (AssemblerX86Shared::HasAVX()) { + masm.vshufps(secondMask, lhs, rhsCopy, out); + } else { + masm.vshufps(secondMask, lhs, rhsCopy, rhsCopy); + masm.moveSimd128Float(rhsCopy, out); + } + return; + } + + // Two elements from one vector, two other elements from the other + MOZ_ASSERT(numLanesFromLHS == 2); + + // TODO Here and below, symmetric case would be more handy to avoid a move, + // but can't be reached because operands would get swapped (bug 1084404). + if (ins->lanesMatch(2, 3, 6, 7)) { + ScratchSimd128Scope scratch(masm); + if (AssemblerX86Shared::HasAVX()) { + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, scratch); + masm.vmovhlps(lhs, rhsCopy, out); + } else { + masm.loadAlignedSimd128Float(rhs, scratch); + masm.vmovhlps(lhs, scratch, scratch); + masm.moveSimd128Float(scratch, out); + } + return; + } + + if (ins->lanesMatch(0, 1, 4, 5)) { + FloatRegister rhsCopy; + ScratchSimd128Scope scratch(masm); + if (rhs.kind() == Operand::FPREG) { + // No need to make an actual copy, since the operand is already + // in a register, and it won't be clobbered by the vmovlhps. + rhsCopy = FloatRegister::FromCode(rhs.fpu()); + } else { + masm.loadAlignedSimd128Float(rhs, scratch); + rhsCopy = scratch; + } + masm.vmovlhps(rhsCopy, lhs, out); + return; + } + + if (ins->lanesMatch(0, 4, 1, 5)) { + masm.vunpcklps(rhs, lhs, out); + return; + } + + // TODO swapped case would be better (bug 1084404) + if (ins->lanesMatch(4, 0, 5, 1)) { + ScratchSimd128Scope scratch(masm); + if (AssemblerX86Shared::HasAVX()) { + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, scratch); + masm.vunpcklps(lhs, rhsCopy, out); + } else { + masm.loadAlignedSimd128Float(rhs, scratch); + masm.vunpcklps(lhs, scratch, scratch); + masm.moveSimd128Float(scratch, out); + } + return; + } + + if (ins->lanesMatch(2, 6, 3, 7)) { + masm.vunpckhps(rhs, lhs, out); + return; + } + + // TODO swapped case would be better (bug 1084404) + if (ins->lanesMatch(6, 2, 7, 3)) { + ScratchSimd128Scope scratch(masm); + if (AssemblerX86Shared::HasAVX()) { + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, scratch); + masm.vunpckhps(lhs, rhsCopy, out); + } else { + masm.loadAlignedSimd128Float(rhs, scratch); + masm.vunpckhps(lhs, scratch, scratch); + masm.moveSimd128Float(scratch, out); + } + return; + } + + // In one vshufps + if (x < 4 && y < 4) { + mask = MacroAssembler::ComputeShuffleMask(x, y, z % 4, w % 4); + masm.vshufps(mask, rhs, lhs, out); + return; + } + + // At creation, we should have explicitly swapped in this case. + MOZ_ASSERT(!(z >= 4 && w >= 4)); + + // In two vshufps, for the most generic case: + uint32_t firstMask[4], secondMask[4]; + unsigned i = 0, j = 2, k = 0; + +#define COMPUTE_MASK(lane) \ + if (lane >= 4) { \ + firstMask[j] = lane % 4; \ + secondMask[k++] = j++; \ + } else { \ + firstMask[i] = lane; \ + secondMask[k++] = i++; \ + } + + COMPUTE_MASK(x) + COMPUTE_MASK(y) + COMPUTE_MASK(z) + COMPUTE_MASK(w) +#undef COMPUTE_MASK + + MOZ_ASSERT(i == 2 && j == 4 && k == 4); + + mask = MacroAssembler::ComputeShuffleMask(firstMask[0], firstMask[1], + firstMask[2], firstMask[3]); + masm.vshufps(mask, rhs, lhs, lhs); + + mask = MacroAssembler::ComputeShuffleMask(secondMask[0], secondMask[1], + secondMask[2], secondMask[3]); + masm.vshufps(mask, lhs, lhs, lhs); +} + +void +CodeGenerator::visitSimdBinaryCompIx16(LSimdBinaryCompIx16* ins) +{ + static const SimdConstant allOnes = SimdConstant::SplatX16(-1); + + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + MOZ_ASSERT_IF(!Assembler::HasAVX(), output == lhs); + + ScratchSimd128Scope scratch(masm); + + MSimdBinaryComp::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryComp::greaterThan: + masm.vpcmpgtb(rhs, lhs, output); + return; + case MSimdBinaryComp::equal: + masm.vpcmpeqb(rhs, lhs, output); + return; + case MSimdBinaryComp::lessThan: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + + // src := src > lhs (i.e. lhs < rhs) + // Improve by doing custom lowering (rhs is tied to the output register) + masm.vpcmpgtb(ToOperand(ins->lhs()), scratch, scratch); + masm.moveSimd128Int(scratch, output); + return; + case MSimdBinaryComp::notEqual: + // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we + // should invert the comparison by, e.g. swapping the arms of a select + // if that's what it's used in. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.vpcmpeqb(rhs, lhs, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + case MSimdBinaryComp::greaterThanOrEqual: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + masm.vpcmpgtb(ToOperand(ins->lhs()), scratch, scratch); + masm.loadConstantSimd128Int(allOnes, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + case MSimdBinaryComp::lessThanOrEqual: + // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.vpcmpgtb(rhs, lhs, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryCompIx8(LSimdBinaryCompIx8* ins) +{ + static const SimdConstant allOnes = SimdConstant::SplatX8(-1); + + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + MOZ_ASSERT_IF(!Assembler::HasAVX(), output == lhs); + + ScratchSimd128Scope scratch(masm); + + MSimdBinaryComp::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryComp::greaterThan: + masm.vpcmpgtw(rhs, lhs, output); + return; + case MSimdBinaryComp::equal: + masm.vpcmpeqw(rhs, lhs, output); + return; + case MSimdBinaryComp::lessThan: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + + // src := src > lhs (i.e. lhs < rhs) + // Improve by doing custom lowering (rhs is tied to the output register) + masm.vpcmpgtw(ToOperand(ins->lhs()), scratch, scratch); + masm.moveSimd128Int(scratch, output); + return; + case MSimdBinaryComp::notEqual: + // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we + // should invert the comparison by, e.g. swapping the arms of a select + // if that's what it's used in. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.vpcmpeqw(rhs, lhs, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + case MSimdBinaryComp::greaterThanOrEqual: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + masm.vpcmpgtw(ToOperand(ins->lhs()), scratch, scratch); + masm.loadConstantSimd128Int(allOnes, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + case MSimdBinaryComp::lessThanOrEqual: + // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.vpcmpgtw(rhs, lhs, output); + masm.bitwiseXorSimd128(Operand(scratch), output); + return; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryCompIx4(LSimdBinaryCompIx4* ins) +{ + static const SimdConstant allOnes = SimdConstant::SplatX4(-1); + + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + MOZ_ASSERT(ToFloatRegister(ins->output()) == lhs); + + ScratchSimd128Scope scratch(masm); + + MSimdBinaryComp::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryComp::greaterThan: + masm.packedGreaterThanInt32x4(rhs, lhs); + return; + case MSimdBinaryComp::equal: + masm.packedEqualInt32x4(rhs, lhs); + return; + case MSimdBinaryComp::lessThan: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + + // src := src > lhs (i.e. lhs < rhs) + // Improve by doing custom lowering (rhs is tied to the output register) + masm.packedGreaterThanInt32x4(ToOperand(ins->lhs()), scratch); + masm.moveSimd128Int(scratch, lhs); + return; + case MSimdBinaryComp::notEqual: + // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we + // should invert the comparison by, e.g. swapping the arms of a select + // if that's what it's used in. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.packedEqualInt32x4(rhs, lhs); + masm.bitwiseXorSimd128(Operand(scratch), lhs); + return; + case MSimdBinaryComp::greaterThanOrEqual: + // src := rhs + if (rhs.kind() == Operand::FPREG) + masm.moveSimd128Int(ToFloatRegister(ins->rhs()), scratch); + else + masm.loadAlignedSimd128Int(rhs, scratch); + masm.packedGreaterThanInt32x4(ToOperand(ins->lhs()), scratch); + masm.loadConstantSimd128Int(allOnes, lhs); + masm.bitwiseXorSimd128(Operand(scratch), lhs); + return; + case MSimdBinaryComp::lessThanOrEqual: + // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. + masm.loadConstantSimd128Int(allOnes, scratch); + masm.packedGreaterThanInt32x4(rhs, lhs); + masm.bitwiseXorSimd128(Operand(scratch), lhs); + return; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryCompFx4(LSimdBinaryCompFx4* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + MSimdBinaryComp::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryComp::equal: + masm.vcmpeqps(rhs, lhs, output); + return; + case MSimdBinaryComp::lessThan: + masm.vcmpltps(rhs, lhs, output); + return; + case MSimdBinaryComp::lessThanOrEqual: + masm.vcmpleps(rhs, lhs, output); + return; + case MSimdBinaryComp::notEqual: + masm.vcmpneqps(rhs, lhs, output); + return; + case MSimdBinaryComp::greaterThanOrEqual: + case MSimdBinaryComp::greaterThan: + // We reverse these before register allocation so that we don't have to + // copy into and out of temporaries after codegen. + MOZ_CRASH("lowering should have reversed this"); + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryArithIx16(LSimdBinaryArithIx16* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + MSimdBinaryArith::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryArith::Op_add: + masm.vpaddb(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_sub: + masm.vpsubb(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_mul: + // 8x16 mul is a valid operation, but not supported in SSE or AVX. + // The operation is synthesized from 16x8 multiplies by + // MSimdBinaryArith::AddLegalized(). + break; + case MSimdBinaryArith::Op_div: + case MSimdBinaryArith::Op_max: + case MSimdBinaryArith::Op_min: + case MSimdBinaryArith::Op_minNum: + case MSimdBinaryArith::Op_maxNum: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryArithIx8(LSimdBinaryArithIx8* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + MSimdBinaryArith::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryArith::Op_add: + masm.vpaddw(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_sub: + masm.vpsubw(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_mul: + masm.vpmullw(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_div: + case MSimdBinaryArith::Op_max: + case MSimdBinaryArith::Op_min: + case MSimdBinaryArith::Op_minNum: + case MSimdBinaryArith::Op_maxNum: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryArithIx4(LSimdBinaryArithIx4* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + ScratchSimd128Scope scratch(masm); + + MSimdBinaryArith::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryArith::Op_add: + masm.vpaddd(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_sub: + masm.vpsubd(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_mul: { + if (AssemblerX86Shared::HasSSE41()) { + masm.vpmulld(rhs, lhs, output); + return; + } + + masm.loadAlignedSimd128Int(rhs, scratch); + masm.vpmuludq(lhs, scratch, scratch); + // scratch contains (Rx, _, Rz, _) where R is the resulting vector. + + FloatRegister temp = ToFloatRegister(ins->temp()); + masm.vpshufd(MacroAssembler::ComputeShuffleMask(1, 1, 3, 3), lhs, lhs); + masm.vpshufd(MacroAssembler::ComputeShuffleMask(1, 1, 3, 3), rhs, temp); + masm.vpmuludq(temp, lhs, lhs); + // lhs contains (Ry, _, Rw, _) where R is the resulting vector. + + masm.vshufps(MacroAssembler::ComputeShuffleMask(0, 2, 0, 2), scratch, lhs, lhs); + // lhs contains (Ry, Rw, Rx, Rz) + masm.vshufps(MacroAssembler::ComputeShuffleMask(2, 0, 3, 1), lhs, lhs, lhs); + return; + } + case MSimdBinaryArith::Op_div: + // x86 doesn't have SIMD i32 div. + break; + case MSimdBinaryArith::Op_max: + // we can do max with a single instruction only if we have SSE4.1 + // using the PMAXSD instruction. + break; + case MSimdBinaryArith::Op_min: + // we can do max with a single instruction only if we have SSE4.1 + // using the PMINSD instruction. + break; + case MSimdBinaryArith::Op_minNum: + case MSimdBinaryArith::Op_maxNum: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryArithFx4(LSimdBinaryArithFx4* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + ScratchSimd128Scope scratch(masm); + + MSimdBinaryArith::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryArith::Op_add: + masm.vaddps(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_sub: + masm.vsubps(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_mul: + masm.vmulps(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_div: + masm.vdivps(rhs, lhs, output); + return; + case MSimdBinaryArith::Op_max: { + FloatRegister lhsCopy = masm.reusedInputFloat32x4(lhs, scratch); + masm.vcmpunordps(rhs, lhsCopy, scratch); + + FloatRegister tmp = ToFloatRegister(ins->temp()); + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, tmp); + masm.vmaxps(Operand(lhs), rhsCopy, tmp); + masm.vmaxps(rhs, lhs, output); + + masm.vandps(tmp, output, output); + masm.vorps(scratch, output, output); // or in the all-ones NaNs + return; + } + case MSimdBinaryArith::Op_min: { + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, scratch); + masm.vminps(Operand(lhs), rhsCopy, scratch); + masm.vminps(rhs, lhs, output); + masm.vorps(scratch, output, output); // NaN or'd with arbitrary bits is NaN + return; + } + case MSimdBinaryArith::Op_minNum: { + FloatRegister tmp = ToFloatRegister(ins->temp()); + masm.loadConstantSimd128Int(SimdConstant::SplatX4(int32_t(0x80000000)), tmp); + + FloatRegister mask = scratch; + FloatRegister tmpCopy = masm.reusedInputFloat32x4(tmp, scratch); + masm.vpcmpeqd(Operand(lhs), tmpCopy, mask); + masm.vandps(tmp, mask, mask); + + FloatRegister lhsCopy = masm.reusedInputFloat32x4(lhs, tmp); + masm.vminps(rhs, lhsCopy, tmp); + masm.vorps(mask, tmp, tmp); + + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, mask); + masm.vcmpneqps(rhs, rhsCopy, mask); + + if (AssemblerX86Shared::HasAVX()) { + masm.vblendvps(mask, lhs, tmp, output); + } else { + // Emulate vblendvps. + // With SSE.4.1 we could use blendvps, however it's awkward since + // it requires the mask to be in xmm0. + if (lhs != output) + masm.moveSimd128Float(lhs, output); + masm.vandps(Operand(mask), output, output); + masm.vandnps(Operand(tmp), mask, mask); + masm.vorps(Operand(mask), output, output); + } + return; + } + case MSimdBinaryArith::Op_maxNum: { + FloatRegister mask = scratch; + masm.loadConstantSimd128Int(SimdConstant::SplatX4(0), mask); + masm.vpcmpeqd(Operand(lhs), mask, mask); + + FloatRegister tmp = ToFloatRegister(ins->temp()); + masm.loadConstantSimd128Int(SimdConstant::SplatX4(int32_t(0x80000000)), tmp); + masm.vandps(tmp, mask, mask); + + FloatRegister lhsCopy = masm.reusedInputFloat32x4(lhs, tmp); + masm.vmaxps(rhs, lhsCopy, tmp); + masm.vandnps(Operand(tmp), mask, mask); + + // Ensure tmp always contains the temporary result + mask = tmp; + tmp = scratch; + + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, mask); + masm.vcmpneqps(rhs, rhsCopy, mask); + + if (AssemblerX86Shared::HasAVX()) { + masm.vblendvps(mask, lhs, tmp, output); + } else { + // Emulate vblendvps. + // With SSE.4.1 we could use blendvps, however it's awkward since + // it requires the mask to be in xmm0. + if (lhs != output) + masm.moveSimd128Float(lhs, output); + masm.vandps(Operand(mask), output, output); + masm.vandnps(Operand(tmp), mask, mask); + masm.vorps(Operand(mask), output, output); + } + return; + } + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinarySaturating(LSimdBinarySaturating* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + SimdSign sign = ins->signedness(); + MOZ_ASSERT(sign != SimdSign::NotApplicable); + + switch (ins->type()) { + case MIRType::Int8x16: + switch (ins->operation()) { + case MSimdBinarySaturating::add: + if (sign == SimdSign::Signed) + masm.vpaddsb(rhs, lhs, output); + else + masm.vpaddusb(rhs, lhs, output); + return; + case MSimdBinarySaturating::sub: + if (sign == SimdSign::Signed) + masm.vpsubsb(rhs, lhs, output); + else + masm.vpsubusb(rhs, lhs, output); + return; + } + break; + + case MIRType::Int16x8: + switch (ins->operation()) { + case MSimdBinarySaturating::add: + if (sign == SimdSign::Signed) + masm.vpaddsw(rhs, lhs, output); + else + masm.vpaddusw(rhs, lhs, output); + return; + case MSimdBinarySaturating::sub: + if (sign == SimdSign::Signed) + masm.vpsubsw(rhs, lhs, output); + else + masm.vpsubusw(rhs, lhs, output); + return; + } + break; + + default: + break; + } + MOZ_CRASH("unsupported type for SIMD saturating arithmetic"); +} + +void +CodeGenerator::visitSimdUnaryArithIx16(LSimdUnaryArithIx16* ins) +{ + Operand in = ToOperand(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + + static const SimdConstant allOnes = SimdConstant::SplatX16(-1); + + switch (ins->operation()) { + case MSimdUnaryArith::neg: + masm.zeroSimd128Int(out); + masm.packedSubInt8(in, out); + return; + case MSimdUnaryArith::not_: + masm.loadConstantSimd128Int(allOnes, out); + masm.bitwiseXorSimd128(in, out); + return; + case MSimdUnaryArith::abs: + case MSimdUnaryArith::reciprocalApproximation: + case MSimdUnaryArith::reciprocalSqrtApproximation: + case MSimdUnaryArith::sqrt: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdUnaryArithIx8(LSimdUnaryArithIx8* ins) +{ + Operand in = ToOperand(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + + static const SimdConstant allOnes = SimdConstant::SplatX8(-1); + + switch (ins->operation()) { + case MSimdUnaryArith::neg: + masm.zeroSimd128Int(out); + masm.packedSubInt16(in, out); + return; + case MSimdUnaryArith::not_: + masm.loadConstantSimd128Int(allOnes, out); + masm.bitwiseXorSimd128(in, out); + return; + case MSimdUnaryArith::abs: + case MSimdUnaryArith::reciprocalApproximation: + case MSimdUnaryArith::reciprocalSqrtApproximation: + case MSimdUnaryArith::sqrt: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdUnaryArithIx4(LSimdUnaryArithIx4* ins) +{ + Operand in = ToOperand(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + + static const SimdConstant allOnes = SimdConstant::SplatX4(-1); + + switch (ins->operation()) { + case MSimdUnaryArith::neg: + masm.zeroSimd128Int(out); + masm.packedSubInt32(in, out); + return; + case MSimdUnaryArith::not_: + masm.loadConstantSimd128Int(allOnes, out); + masm.bitwiseXorSimd128(in, out); + return; + case MSimdUnaryArith::abs: + case MSimdUnaryArith::reciprocalApproximation: + case MSimdUnaryArith::reciprocalSqrtApproximation: + case MSimdUnaryArith::sqrt: + break; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdUnaryArithFx4(LSimdUnaryArithFx4* ins) +{ + Operand in = ToOperand(ins->input()); + FloatRegister out = ToFloatRegister(ins->output()); + + // All ones but the sign bit + float signMask = SpecificNaN(0, FloatingPoint::kSignificandBits); + static const SimdConstant signMasks = SimdConstant::SplatX4(signMask); + + // All ones including the sign bit + float ones = SpecificNaN(1, FloatingPoint::kSignificandBits); + static const SimdConstant allOnes = SimdConstant::SplatX4(ones); + + // All zeros but the sign bit + static const SimdConstant minusZero = SimdConstant::SplatX4(-0.f); + + switch (ins->operation()) { + case MSimdUnaryArith::abs: + masm.loadConstantSimd128Float(signMasks, out); + masm.bitwiseAndSimd128(in, out); + return; + case MSimdUnaryArith::neg: + masm.loadConstantSimd128Float(minusZero, out); + masm.bitwiseXorSimd128(in, out); + return; + case MSimdUnaryArith::not_: + masm.loadConstantSimd128Float(allOnes, out); + masm.bitwiseXorSimd128(in, out); + return; + case MSimdUnaryArith::reciprocalApproximation: + masm.packedRcpApproximationFloat32x4(in, out); + return; + case MSimdUnaryArith::reciprocalSqrtApproximation: + masm.packedRcpSqrtApproximationFloat32x4(in, out); + return; + case MSimdUnaryArith::sqrt: + masm.packedSqrtFloat32x4(in, out); + return; + } + MOZ_CRASH("unexpected SIMD op"); +} + +void +CodeGenerator::visitSimdBinaryBitwise(LSimdBinaryBitwise* ins) +{ + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Operand rhs = ToOperand(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + + MSimdBinaryBitwise::Operation op = ins->operation(); + switch (op) { + case MSimdBinaryBitwise::and_: + if (ins->type() == MIRType::Float32x4) + masm.vandps(rhs, lhs, output); + else + masm.vpand(rhs, lhs, output); + return; + case MSimdBinaryBitwise::or_: + if (ins->type() == MIRType::Float32x4) + masm.vorps(rhs, lhs, output); + else + masm.vpor(rhs, lhs, output); + return; + case MSimdBinaryBitwise::xor_: + if (ins->type() == MIRType::Float32x4) + masm.vxorps(rhs, lhs, output); + else + masm.vpxor(rhs, lhs, output); + return; + } + MOZ_CRASH("unexpected SIMD bitwise op"); +} + +void +CodeGenerator::visitSimdShift(LSimdShift* ins) +{ + FloatRegister out = ToFloatRegister(ins->output()); + MOZ_ASSERT(ToFloatRegister(ins->vector()) == out); // defineReuseInput(0); + + // The shift amount is masked to the number of bits in a lane. + uint32_t shiftmask = (128u / SimdTypeToLength(ins->type())) - 1; + + // Note that SSE doesn't have instructions for shifting 8x16 vectors. + // These shifts are synthesized by the MSimdShift::AddLegalized() function. + const LAllocation* val = ins->value(); + if (val->isConstant()) { + MOZ_ASSERT(ins->temp()->isBogusTemp()); + Imm32 count(uint32_t(ToInt32(val)) & shiftmask); + switch (ins->type()) { + case MIRType::Int16x8: + switch (ins->operation()) { + case MSimdShift::lsh: + masm.packedLeftShiftByScalarInt16x8(count, out); + return; + case MSimdShift::rsh: + masm.packedRightShiftByScalarInt16x8(count, out); + return; + case MSimdShift::ursh: + masm.packedUnsignedRightShiftByScalarInt16x8(count, out); + return; + } + break; + case MIRType::Int32x4: + switch (ins->operation()) { + case MSimdShift::lsh: + masm.packedLeftShiftByScalarInt32x4(count, out); + return; + case MSimdShift::rsh: + masm.packedRightShiftByScalarInt32x4(count, out); + return; + case MSimdShift::ursh: + masm.packedUnsignedRightShiftByScalarInt32x4(count, out); + return; + } + break; + default: + MOZ_CRASH("unsupported type for SIMD shifts"); + } + MOZ_CRASH("unexpected SIMD bitwise op"); + } + + // Truncate val to 5 bits. We should have a temp register for that. + MOZ_ASSERT(val->isRegister()); + Register count = ToRegister(ins->temp()); + masm.mov(ToRegister(val), count); + masm.andl(Imm32(shiftmask), count); + ScratchFloat32Scope scratch(masm); + masm.vmovd(count, scratch); + + switch (ins->type()) { + case MIRType::Int16x8: + switch (ins->operation()) { + case MSimdShift::lsh: + masm.packedLeftShiftByScalarInt16x8(scratch, out); + return; + case MSimdShift::rsh: + masm.packedRightShiftByScalarInt16x8(scratch, out); + return; + case MSimdShift::ursh: + masm.packedUnsignedRightShiftByScalarInt16x8(scratch, out); + return; + } + break; + case MIRType::Int32x4: + switch (ins->operation()) { + case MSimdShift::lsh: + masm.packedLeftShiftByScalarInt32x4(scratch, out); + return; + case MSimdShift::rsh: + masm.packedRightShiftByScalarInt32x4(scratch, out); + return; + case MSimdShift::ursh: + masm.packedUnsignedRightShiftByScalarInt32x4(scratch, out); + return; + } + break; + default: + MOZ_CRASH("unsupported type for SIMD shifts"); + } + MOZ_CRASH("unexpected SIMD bitwise op"); +} + +void +CodeGenerator::visitSimdSelect(LSimdSelect* ins) +{ + FloatRegister mask = ToFloatRegister(ins->mask()); + FloatRegister onTrue = ToFloatRegister(ins->lhs()); + FloatRegister onFalse = ToFloatRegister(ins->rhs()); + FloatRegister output = ToFloatRegister(ins->output()); + FloatRegister temp = ToFloatRegister(ins->temp()); + + if (onTrue != output) + masm.vmovaps(onTrue, output); + if (mask != temp) + masm.vmovaps(mask, temp); + + MSimdSelect* mir = ins->mir(); + unsigned lanes = SimdTypeToLength(mir->type()); + + if (AssemblerX86Shared::HasAVX() && lanes == 4) { + // TBD: Use vpblendvb for lanes > 4, HasAVX. + masm.vblendvps(mask, onTrue, onFalse, output); + return; + } + + // SSE4.1 has plain blendvps which can do this, but it is awkward + // to use because it requires the mask to be in xmm0. + + masm.bitwiseAndSimd128(Operand(temp), output); + masm.bitwiseAndNotSimd128(Operand(onFalse), temp); + masm.bitwiseOrSimd128(Operand(temp), output); +} + void CodeGenerator::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) { @@ -2646,6 +4424,15 @@ CodeGeneratorX86Shared::canonicalizeIfDeterministic(Scalar::Type type, const LAl masm.canonicalizeDoubleIfDeterministic(in); break; } + case Scalar::Float32x4: { + FloatRegister in = ToFloatRegister(value); + MOZ_ASSERT(in.isSimd128()); + FloatRegister scratch = in != xmm0.asSimd128() ? xmm0 : xmm1; + masm.push(scratch); + masm.canonicalizeFloat32x4(in, scratch); + masm.pop(scratch); + break; + } default: { // Other types don't need canonicalization. break; diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h index 21cd6540d61e..01c1883ec157 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -51,6 +51,31 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared } }; + // Additional bounds check for vector Float to Int conversion, when the + // undefined pattern is seen. Might imply a bailout. + class OutOfLineSimdFloatToIntCheck : public OutOfLineCodeBase + { + Register temp_; + FloatRegister input_; + LInstruction* ins_; + wasm::BytecodeOffset bytecodeOffset_; + + public: + OutOfLineSimdFloatToIntCheck(Register temp, FloatRegister input, LInstruction *ins, + wasm::BytecodeOffset bytecodeOffset) + : temp_(temp), input_(input), ins_(ins), bytecodeOffset_(bytecodeOffset) + {} + + Register temp() const { return temp_; } + FloatRegister input() const { return input_; } + LInstruction* ins() const { return ins_; } + wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; } + + void accept(CodeGeneratorX86Shared* codegen) override { + codegen->visitOutOfLineSimdFloatToIntCheck(this); + } + }; + NonAssertingLabel deoptLabel_; Operand ToOperand(const LAllocation& a); @@ -148,6 +173,14 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base); + void emitSimdExtractLane8x16(FloatRegister input, Register output, unsigned lane, + SimdSign signedness); + void emitSimdExtractLane16x8(FloatRegister input, Register output, unsigned lane, + SimdSign signedness); + void emitSimdExtractLane32x4(FloatRegister input, Register output, unsigned lane); + + template void visitSimdGeneralShuffle(LSimdGeneralShuffleBase* lir, Reg temp); + void generateInvalidateEpilogue(); void canonicalizeIfDeterministic(Scalar::Type type, const LAllocation* value); @@ -160,6 +193,7 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared void visitModOverflowCheck(ModOverflowCheck* ool); void visitReturnZero(ReturnZero* ool); void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool); + void visitOutOfLineSimdFloatToIntCheck(OutOfLineSimdFloatToIntCheck* ool); void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool); void visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool); }; diff --git a/js/src/jit/x86-shared/LIR-x86-shared.h b/js/src/jit/x86-shared/LIR-x86-shared.h index 4ee72ff0ebf4..8dbd4fde62fc 100644 --- a/js/src/jit/x86-shared/LIR-x86-shared.h +++ b/js/src/jit/x86-shared/LIR-x86-shared.h @@ -344,6 +344,49 @@ class LMulI : public LBinaryMath<0, 1> } }; +// Constructs an int32x4 SIMD value. +class LSimdValueInt32x4 : public LInstructionHelper<1, 4, 0> +{ + public: + LIR_HEADER(SimdValueInt32x4) + LSimdValueInt32x4(const LAllocation& x, const LAllocation& y, + const LAllocation& z, const LAllocation& w) + : LInstructionHelper(classOpcode) + { + setOperand(0, x); + setOperand(1, y); + setOperand(2, z); + setOperand(3, w); + } + + MSimdValueX4* mir() const { + return mir_->toSimdValueX4(); + } +}; + +// Constructs a float32x4 SIMD value, optimized for x86 family +class LSimdValueFloat32x4 : public LInstructionHelper<1, 4, 1> +{ + public: + LIR_HEADER(SimdValueFloat32x4) + LSimdValueFloat32x4(const LAllocation& x, const LAllocation& y, + const LAllocation& z, const LAllocation& w, + const LDefinition& copyY) + : LInstructionHelper(classOpcode) + { + setOperand(0, x); + setOperand(1, y); + setOperand(2, z); + setOperand(3, w); + + setTemp(0, copyY); + } + + MSimdValueX4* mir() const { + return mir_->toSimdValueX4(); + } +}; + class LInt64ToFloatingPoint : public LInstructionHelper<1, INT64_PIECES, 1> { public: diff --git a/js/src/jit/x86-shared/Lowering-x86-shared.cpp b/js/src/jit/x86-shared/Lowering-x86-shared.cpp index c0b3e10014e9..7a47f155493d 100644 --- a/js/src/jit/x86-shared/Lowering-x86-shared.cpp +++ b/js/src/jit/x86-shared/Lowering-x86-shared.cpp @@ -136,6 +136,31 @@ template void LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, 0>* in template void LIRGeneratorX86Shared::lowerForFPU(LInstructionHelper<1, 2, 1>* ins, MDefinition* mir, MDefinition* lhs, MDefinition* rhs); +void +LIRGeneratorX86Shared::lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, MDefinition* lhs, MDefinition* rhs) +{ + lowerForALU(ins, mir, lhs, rhs); +} + +void +LIRGeneratorX86Shared::lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, MDefinition* lhs, MDefinition* rhs) +{ + // Swap the operands around to fit the instructions that x86 actually has. + // We do this here, before register allocation, so that we don't need + // temporaries and copying afterwards. + switch (mir->operation()) { + case MSimdBinaryComp::greaterThan: + case MSimdBinaryComp::greaterThanOrEqual: + mir->reverse(); + Swap(lhs, rhs); + break; + default: + break; + } + + lowerForFPU(ins, mir, lhs, rhs); +} + void LIRGeneratorX86Shared::lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir, MDefinition* lhs, MDefinition* rhs) @@ -585,6 +610,358 @@ LIRGeneratorX86Shared::lowerAtomicTypedArrayElementBinop(MAtomicTypedArrayElemen define(lir, ins); } +void +LIRGenerator::visitSimdInsertElement(MSimdInsertElement* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + + LUse vec = useRegisterAtStart(ins->vector()); + LUse val = useRegister(ins->value()); + switch (ins->type()) { + case MIRType::Int8x16: + case MIRType::Bool8x16: + // When SSE 4.1 is not available, we need to go via the stack. + // This requires the value to be inserted to be in %eax-%edx. + // Pick %ebx since other instructions use %eax or %ecx hard-wired. +#if defined(JS_CODEGEN_X86) + if (!AssemblerX86Shared::HasSSE41()) + val = useFixed(ins->value(), ebx); +#endif + defineReuseInput(new(alloc()) LSimdInsertElementI(vec, val), ins, 0); + break; + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + defineReuseInput(new(alloc()) LSimdInsertElementI(vec, val), ins, 0); + break; + case MIRType::Float32x4: + defineReuseInput(new(alloc()) LSimdInsertElementF(vec, val), ins, 0); + break; + default: + MOZ_CRASH("Unknown SIMD kind when generating constant"); + } +} + +void +LIRGenerator::visitSimdExtractElement(MSimdExtractElement* ins) +{ + MOZ_ASSERT(IsSimdType(ins->input()->type())); + MOZ_ASSERT(!IsSimdType(ins->type())); + + switch (ins->input()->type()) { + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: { + MOZ_ASSERT(ins->signedness() != SimdSign::NotApplicable); + LUse use = useRegisterAtStart(ins->input()); + if (ins->type() == MIRType::Double) { + // Extract an Uint32 lane into a double. + MOZ_ASSERT(ins->signedness() == SimdSign::Unsigned); + define(new (alloc()) LSimdExtractElementU2D(use, temp()), ins); + } else { + auto* lir = new (alloc()) LSimdExtractElementI(use); +#if defined(JS_CODEGEN_X86) + // On x86 (32-bit), we may need to use movsbl or movzbl instructions + // to sign or zero extend the extracted lane to 32 bits. The 8-bit + // version of these instructions require a source register that is + // %al, %bl, %cl, or %dl. + // Fix it to %ebx since we can't express that constraint better. + if (ins->input()->type() == MIRType::Int8x16) { + defineFixed(lir, ins, LAllocation(AnyRegister(ebx))); + return; + } +#endif + define(lir, ins); + } + break; + } + case MIRType::Float32x4: { + MOZ_ASSERT(ins->signedness() == SimdSign::NotApplicable); + LUse use = useRegisterAtStart(ins->input()); + define(new(alloc()) LSimdExtractElementF(use), ins); + break; + } + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: { + MOZ_ASSERT(ins->signedness() == SimdSign::NotApplicable); + LUse use = useRegisterAtStart(ins->input()); + define(new(alloc()) LSimdExtractElementB(use), ins); + break; + } + default: + MOZ_CRASH("Unknown SIMD kind when extracting element"); + } +} + +void +LIRGenerator::visitSimdBinaryArith(MSimdBinaryArith* ins) +{ + MOZ_ASSERT(IsSimdType(ins->lhs()->type())); + MOZ_ASSERT(IsSimdType(ins->rhs()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + + MDefinition* lhs = ins->lhs(); + MDefinition* rhs = ins->rhs(); + + if (ins->isCommutative()) + ReorderCommutative(&lhs, &rhs, ins); + + switch (ins->type()) { + case MIRType::Int8x16: { + LSimdBinaryArithIx16* lir = new (alloc()) LSimdBinaryArithIx16(); + lir->setTemp(0, LDefinition::BogusTemp()); + lowerForFPU(lir, ins, lhs, rhs); + return; + } + + case MIRType::Int16x8: { + LSimdBinaryArithIx8* lir = new (alloc()) LSimdBinaryArithIx8(); + lir->setTemp(0, LDefinition::BogusTemp()); + lowerForFPU(lir, ins, lhs, rhs); + return; + } + + case MIRType::Int32x4: { + LSimdBinaryArithIx4* lir = new (alloc()) LSimdBinaryArithIx4(); + bool needsTemp = + ins->operation() == MSimdBinaryArith::Op_mul && !MacroAssembler::HasSSE41(); + lir->setTemp(0, needsTemp ? temp(LDefinition::SIMD128INT) : LDefinition::BogusTemp()); + lowerForFPU(lir, ins, lhs, rhs); + return; + } + + case MIRType::Float32x4: { + LSimdBinaryArithFx4* lir = new (alloc()) LSimdBinaryArithFx4(); + + bool needsTemp = ins->operation() == MSimdBinaryArith::Op_max || + ins->operation() == MSimdBinaryArith::Op_minNum || + ins->operation() == MSimdBinaryArith::Op_maxNum; + lir->setTemp(0, + needsTemp ? temp(LDefinition::SIMD128FLOAT) : LDefinition::BogusTemp()); + lowerForFPU(lir, ins, lhs, rhs); + return; + } + + default: + MOZ_CRASH("unknown simd type on binary arith operation"); + } +} + +void +LIRGenerator::visitSimdBinarySaturating(MSimdBinarySaturating* ins) +{ + MOZ_ASSERT(IsSimdType(ins->lhs()->type())); + MOZ_ASSERT(IsSimdType(ins->rhs()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + + MDefinition* lhs = ins->lhs(); + MDefinition* rhs = ins->rhs(); + + if (ins->isCommutative()) + ReorderCommutative(&lhs, &rhs, ins); + + LSimdBinarySaturating* lir = new (alloc()) LSimdBinarySaturating(); + lowerForFPU(lir, ins, lhs, rhs); +} + +void +LIRGenerator::visitSimdSelect(MSimdSelect* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + + LSimdSelect* lins = new(alloc()) LSimdSelect; + MDefinition* r0 = ins->getOperand(0); + MDefinition* r1 = ins->getOperand(1); + MDefinition* r2 = ins->getOperand(2); + + lins->setOperand(0, useRegister(r0)); + lins->setOperand(1, useRegister(r1)); + lins->setOperand(2, useRegister(r2)); + lins->setTemp(0, temp(LDefinition::SIMD128FLOAT)); + + define(lins, ins); +} + +void +LIRGenerator::visitSimdSplat(MSimdSplat* ins) +{ + LAllocation x = useRegisterAtStart(ins->getOperand(0)); + + switch (ins->type()) { + case MIRType::Int8x16: + define(new (alloc()) LSimdSplatX16(x), ins); + break; + case MIRType::Int16x8: + define(new (alloc()) LSimdSplatX8(x), ins); + break; + case MIRType::Int32x4: + case MIRType::Float32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + // Use the SplatX4 instruction for all boolean splats. Since the input + // value is a 32-bit int that is either 0 or -1, the X4 splat gives + // the right result for all boolean geometries. + // For floats, (Non-AVX) codegen actually wants the input and the output + // to be in the same register, but we can't currently use + // defineReuseInput because they have different types (scalar vs + // vector), so a spill slot for one may not be suitable for the other. + define(new (alloc()) LSimdSplatX4(x), ins); + break; + default: + MOZ_CRASH("Unknown SIMD kind"); + } +} + +void +LIRGenerator::visitSimdValueX4(MSimdValueX4* ins) +{ + switch (ins->type()) { + case MIRType::Float32x4: { + // Ideally, x would be used at start and reused for the output, however + // register allocation currently doesn't permit us to tie together two + // virtual registers with different types. + LAllocation x = useRegister(ins->getOperand(0)); + LAllocation y = useRegister(ins->getOperand(1)); + LAllocation z = useRegister(ins->getOperand(2)); + LAllocation w = useRegister(ins->getOperand(3)); + LDefinition t = temp(LDefinition::SIMD128FLOAT); + define(new (alloc()) LSimdValueFloat32x4(x, y, z, w, t), ins); + break; + } + case MIRType::Bool32x4: + case MIRType::Int32x4: { + // No defineReuseInput => useAtStart for everyone. + LAllocation x = useRegisterAtStart(ins->getOperand(0)); + LAllocation y = useRegisterAtStart(ins->getOperand(1)); + LAllocation z = useRegisterAtStart(ins->getOperand(2)); + LAllocation w = useRegisterAtStart(ins->getOperand(3)); + define(new(alloc()) LSimdValueInt32x4(x, y, z, w), ins); + break; + } + default: + MOZ_CRASH("Unknown SIMD kind"); + } +} + +void +LIRGenerator::visitSimdSwizzle(MSimdSwizzle* ins) +{ + MOZ_ASSERT(IsSimdType(ins->input()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + + if (IsIntegerSimdType(ins->input()->type())) { + LUse use = useRegisterAtStart(ins->input()); + LSimdSwizzleI* lir = new (alloc()) LSimdSwizzleI(use); + define(lir, ins); + // We need a GPR temp register for pre-SSSE3 codegen (no vpshufb). + if (Assembler::HasSSSE3()) { + lir->setTemp(0, LDefinition::BogusTemp()); + } else { + // The temp must be a GPR usable with 8-bit loads and stores. +#if defined(JS_CODEGEN_X86) + lir->setTemp(0, tempFixed(ebx)); +#else + lir->setTemp(0, temp()); +#endif + } + } else if (ins->input()->type() == MIRType::Float32x4) { + LUse use = useRegisterAtStart(ins->input()); + LSimdSwizzleF* lir = new (alloc()) LSimdSwizzleF(use); + define(lir, ins); + lir->setTemp(0, LDefinition::BogusTemp()); + } else { + MOZ_CRASH("Unknown SIMD kind when getting lane"); + } +} + +void +LIRGenerator::visitSimdShuffle(MSimdShuffle* ins) +{ + MOZ_ASSERT(IsSimdType(ins->lhs()->type())); + MOZ_ASSERT(IsSimdType(ins->rhs()->type())); + MOZ_ASSERT(IsSimdType(ins->type())); + if (ins->type() == MIRType::Int32x4 || ins->type() == MIRType::Float32x4) { + bool zFromLHS = ins->lane(2) < 4; + bool wFromLHS = ins->lane(3) < 4; + uint32_t lanesFromLHS = (ins->lane(0) < 4) + (ins->lane(1) < 4) + zFromLHS + wFromLHS; + + LSimdShuffleX4* lir = new (alloc()) LSimdShuffleX4(); + lowerForFPU(lir, ins, ins->lhs(), ins->rhs()); + + // See codegen for requirements details. + LDefinition temp = + (lanesFromLHS == 3) ? tempCopy(ins->rhs(), 1) : LDefinition::BogusTemp(); + lir->setTemp(0, temp); + } else { + MOZ_ASSERT(ins->type() == MIRType::Int8x16 || ins->type() == MIRType::Int16x8); + LSimdShuffle* lir = new (alloc()) LSimdShuffle(); + lir->setOperand(0, useRegister(ins->lhs())); + lir->setOperand(1, useRegister(ins->rhs())); + define(lir, ins); + // We need a GPR temp register for pre-SSSE3 codegen, and an SSE temp + // when using pshufb. + if (Assembler::HasSSSE3()) { + lir->setTemp(0, temp(LDefinition::SIMD128INT)); + } else { + // The temp must be a GPR usable with 8-bit loads and stores. +#if defined(JS_CODEGEN_X86) + lir->setTemp(0, tempFixed(ebx)); +#else + lir->setTemp(0, temp()); +#endif + } + } +} + +void +LIRGenerator::visitSimdGeneralShuffle(MSimdGeneralShuffle* ins) +{ + MOZ_ASSERT(IsSimdType(ins->type())); + + size_t numOperands = ins->numVectors() + ins->numLanes(); + + LSimdGeneralShuffleBase* lir; + if (IsIntegerSimdType(ins->type())) { +#if defined(JS_CODEGEN_X86) + // The temp register must be usable with 8-bit load and store + // instructions, so one of %eax-%edx. + LDefinition t; + if (ins->type() == MIRType::Int8x16) + t = tempFixed(ebx); + else + t = temp(); +#else + LDefinition t = temp(); +#endif + lir = allocateVariadic(numOperands, t); + } else if (ins->type() == MIRType::Float32x4) { + lir = allocateVariadic(numOperands, temp()); + } else { + MOZ_CRASH("Unknown SIMD kind when doing a shuffle"); + } + + if (!lir) + return; + + for (unsigned i = 0; i < ins->numVectors(); i++) { + MOZ_ASSERT(IsSimdType(ins->vector(i)->type())); + lir->setOperand(i, useRegister(ins->vector(i))); + } + + for (unsigned i = 0; i < ins->numLanes(); i++) { + MOZ_ASSERT(ins->lane(i)->type() == MIRType::Int32); + // Note that there can be up to 16 lane arguments, so we can't assume + // that they all get an allocated register. + lir->setOperand(i + ins->numVectors(), use(ins->lane(i))); + } + + assignSnapshot(lir, Bailout_BoundsCheck); + define(lir, ins); +} + void LIRGenerator::visitCopySign(MCopySign* ins) { diff --git a/js/src/jit/x86-shared/Lowering-x86-shared.h b/js/src/jit/x86-shared/Lowering-x86-shared.h index e5177d47b572..10fd9e489697 100644 --- a/js/src/jit/x86-shared/Lowering-x86-shared.h +++ b/js/src/jit/x86-shared/Lowering-x86-shared.h @@ -36,6 +36,10 @@ class LIRGeneratorX86Shared : public LIRGeneratorShared template void lowerForFPU(LInstructionHelper<1, 2, Temps>* ins, MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + void lowerForCompIx4(LSimdBinaryCompIx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs); + void lowerForCompFx4(LSimdBinaryCompFx4* ins, MSimdBinaryComp* mir, + MDefinition* lhs, MDefinition* rhs); void lowerForBitAndAndBranch(LBitAndAndBranch* baab, MInstruction* mir, MDefinition* lhs, MDefinition* rhs); void lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs); diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared-SIMD.cpp b/js/src/jit/x86-shared/MacroAssembler-x86-shared-SIMD.cpp deleted file mode 100644 index 0fbc30cb66b7..000000000000 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-SIMD.cpp +++ /dev/null @@ -1,1226 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "jit/MacroAssembler.h" -#include "jit/x86-shared/MacroAssembler-x86-shared.h" - -#include "jit/MacroAssembler-inl.h" - -using namespace js; -using namespace js::jit; - -using mozilla::DebugOnly; -using mozilla::FloatingPoint; -using mozilla::Maybe; -using mozilla::SpecificNaN; - -void -MacroAssemblerX86Shared::checkedConvertFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest, - Register temp, Label* oolEntry, - Label* rejoin) -{ - // Does the conversion and jumps to the OOL entry if the result value - // is the undefined integer pattern. - static const SimdConstant InvalidResult = SimdConstant::SplatX4(int32_t(-2147483648)); - convertFloat32x4ToInt32x4(src, dest); - - ScratchSimd128Scope scratch(asMasm()); - asMasm().loadConstantSimd128Int(InvalidResult, scratch); - packedEqualInt32x4(Operand(dest), scratch); - // TODO (bug 1156228): If we have SSE4.1, we can use PTEST here instead of - // the two following instructions. - vmovmskps(scratch, temp); - cmp32(temp, Imm32(0)); - j(Assembler::NotEqual, oolEntry); - bind(rejoin); -} - -void -MacroAssemblerX86Shared::oolConvertFloat32x4ToInt32x4(FloatRegister src, Register temp, - Label* rejoin, Label* onConversionError) -{ - static const SimdConstant Int32MaxX4 = SimdConstant::SplatX4(2147483647.f); - static const SimdConstant Int32MinX4 = SimdConstant::SplatX4(-2147483648.f); - - ScratchSimd128Scope scratch(asMasm()); - asMasm().loadConstantSimd128Float(Int32MinX4, scratch); - vcmpleps(Operand(src), scratch, scratch); - vmovmskps(scratch, temp); - cmp32(temp, Imm32(15)); - j(Assembler::NotEqual, onConversionError); - - asMasm().loadConstantSimd128Float(Int32MaxX4, scratch); - vcmpleps(Operand(src), scratch, scratch); - vmovmskps(scratch, temp); - cmp32(temp, Imm32(0)); - j(Assembler::NotEqual, onConversionError); - - jump(rejoin); -} - -void -MacroAssemblerX86Shared::checkedConvertFloat32x4ToUint32x4(FloatRegister in, FloatRegister out, Register temp, FloatRegister tempF, Label* failed) -{ - // Classify lane values into 4 disjoint classes: - // - // N-lanes: in <= -1.0 - // A-lanes: -1.0 < in <= 0x0.ffffffp31 - // B-lanes: 0x1.0p31 <= in <= 0x0.ffffffp32 - // V-lanes: 0x1.0p32 <= in, or isnan(in) - // - // We need to bail out to throw a RangeError if we see any N-lanes or - // V-lanes. - // - // For A-lanes and B-lanes, we make two float -> int32 conversions: - // - // A = cvttps2dq(in) - // B = cvttps2dq(in - 0x1.0p31f) - // - // Note that the subtraction for the B computation is exact for B-lanes. - // There is no rounding, so B is the low 31 bits of the correctly converted - // result. - // - // The cvttps2dq instruction produces 0x80000000 when the input is NaN or - // out of range for a signed int32_t. This conveniently provides the missing - // high bit for B, so the desired result is A for A-lanes and A|B for - // B-lanes. - - ScratchSimd128Scope scratch(asMasm()); - - // TODO: If the majority of lanes are A-lanes, it could be faster to compute - // A first, use vmovmskps to check for any non-A-lanes and handle them in - // ool code. OTOH, we we're wrong about the lane distribution, that would be - // slower. - - // Compute B in |scratch|. - static const float Adjust = 0x80000000; // 0x1.0p31f for the benefit of MSVC. - static const SimdConstant Bias = SimdConstant::SplatX4(-Adjust); - asMasm().loadConstantSimd128Float(Bias, scratch); - packedAddFloat32(Operand(in), scratch); - convertFloat32x4ToInt32x4(scratch, scratch); - - // Compute A in |out|. This is the last time we use |in| and the first time - // we use |out|, so we can tolerate if they are the same register. - convertFloat32x4ToInt32x4(in, out); - - // We can identify A-lanes by the sign bits in A: Any A-lanes will be - // positive in A, and N, B, and V-lanes will be 0x80000000 in A. Compute a - // mask of non-A-lanes into |tempF|. - zeroSimd128Float(tempF); - packedGreaterThanInt32x4(Operand(out), tempF); - - // Clear the A-lanes in B. - bitwiseAndSimdInt(scratch, Operand(tempF), scratch); - - // Compute the final result: A for A-lanes, A|B for B-lanes. - bitwiseOrSimdInt(out, Operand(scratch), out); - - // We still need to filter out the V-lanes. They would show up as 0x80000000 - // in both A and B. Since we cleared the valid A-lanes in B, the V-lanes are - // the remaining negative lanes in B. - vmovmskps(scratch, temp); - cmp32(temp, Imm32(0)); - j(Assembler::NotEqual, failed); -} - -void -MacroAssemblerX86Shared::createInt32x4(Register lane0, Register lane1, Register lane2, - Register lane3, FloatRegister dest) -{ - if (AssemblerX86Shared::HasSSE41()) { - vmovd(lane0, dest); - vpinsrd(1, lane1, dest, dest); - vpinsrd(2, lane2, dest, dest); - vpinsrd(3, lane3, dest, dest); - return; - } - - asMasm().reserveStack(Simd128DataSize); - store32(lane0, Address(StackPointer, 0 * sizeof(int32_t))); - store32(lane1, Address(StackPointer, 1 * sizeof(int32_t))); - store32(lane2, Address(StackPointer, 2 * sizeof(int32_t))); - store32(lane3, Address(StackPointer, 3 * sizeof(int32_t))); - loadAlignedSimd128Int(Address(StackPointer, 0), dest); - asMasm().freeStack(Simd128DataSize); -} - -void -MacroAssemblerX86Shared::createFloat32x4(FloatRegister lane0, FloatRegister lane1, - FloatRegister lane2, FloatRegister lane3, - FloatRegister temp, FloatRegister output) -{ - FloatRegister lane0Copy = reusedInputFloat32x4(lane0, output); - FloatRegister lane1Copy = reusedInputFloat32x4(lane1, temp); - vunpcklps(lane3, lane1Copy, temp); - vunpcklps(lane2, lane0Copy, output); - vunpcklps(temp, output, output); -} - -void -MacroAssemblerX86Shared::splatX16(Register input, FloatRegister output) -{ - vmovd(input, output); - if (AssemblerX86Shared::HasSSSE3()) { - zeroSimd128Int(ScratchSimd128Reg); - vpshufb(ScratchSimd128Reg, output, output); - } else { - // Use two shifts to duplicate the low 8 bits into the low 16 bits. - vpsllw(Imm32(8), output, output); - vmovdqa(output, ScratchSimd128Reg); - vpsrlw(Imm32(8), ScratchSimd128Reg, ScratchSimd128Reg); - vpor(ScratchSimd128Reg, output, output); - // Then do an X8 splat. - vpshuflw(0, output, output); - vpshufd(0, output, output); - } -} - -void -MacroAssemblerX86Shared::splatX8(Register input, FloatRegister output) -{ - vmovd(input, output); - vpshuflw(0, output, output); - vpshufd(0, output, output); -} - -void -MacroAssemblerX86Shared::splatX4(Register input, FloatRegister output) -{ - vmovd(input, output); - vpshufd(0, output, output); -} - -void -MacroAssemblerX86Shared::splatX4(FloatRegister input, FloatRegister output) -{ - FloatRegister inputCopy = reusedInputFloat32x4(input, output); - vshufps(0, inputCopy, inputCopy, output); -} - -void -MacroAssemblerX86Shared::reinterpretSimd(bool isIntegerLaneType, FloatRegister input, - FloatRegister output) -{ - if (input.aliases(output)) - return; - if (isIntegerLaneType) - vmovdqa(input, output); - else - vmovaps(input, output); -} - -void -MacroAssemblerX86Shared::extractLaneInt32x4(FloatRegister input, Register output, unsigned lane) -{ - if (lane == 0) { - // The value we want to extract is in the low double-word - moveLowInt32(input, output); - } else if (AssemblerX86Shared::HasSSE41()) { - vpextrd(lane, input, output); - } else { - uint32_t mask = MacroAssembler::ComputeShuffleMask(lane); - shuffleInt32(mask, input, ScratchSimd128Reg); - moveLowInt32(ScratchSimd128Reg, output); - } -} - -void -MacroAssemblerX86Shared::extractLaneFloat32x4(FloatRegister input, FloatRegister output, - unsigned lane, bool canonicalize) -{ - if (lane == 0) { - // The value we want to extract is in the low double-word - if (input != output) - moveFloat32(input, output); - } else if (lane == 2) { - moveHighPairToLowPairFloat32(input, output); - } else { - uint32_t mask = MacroAssembler::ComputeShuffleMask(lane); - shuffleFloat32(mask, input, output); - } - // NaNs contained within SIMD values are not enforced to be canonical, so - // when we extract an element into a "regular" scalar JS value, we have to - // canonicalize. In wasm code, we can skip this, as wasm only has to - // canonicalize NaNs at FFI boundaries. - if (canonicalize) - asMasm().canonicalizeFloat(output); -} - -void -MacroAssemblerX86Shared::extractLaneInt16x8(FloatRegister input, Register output, unsigned lane, - SimdSign sign) -{ - // Unlike pextrd and pextrb, this is available in SSE2. - vpextrw(lane, input, output); - if (sign == SimdSign::Signed) - movswl(output, output); -} - -void -MacroAssemblerX86Shared::extractLaneInt8x16(FloatRegister input, Register output, unsigned lane, - SimdSign sign) -{ - if (AssemblerX86Shared::HasSSE41()) { - vpextrb(lane, input, output); - // vpextrb clears the high bits, so no further extension required. - if (sign == SimdSign::Unsigned) - sign = SimdSign::NotApplicable; - } else { - // Extract the relevant 16 bits containing our lane, then shift the - // right 8 bits into place. - extractLaneInt16x8(input, output, lane / 2, SimdSign::Unsigned); - if (lane % 2) { - shrl(Imm32(8), output); - // The shrl handles the zero-extension. Don't repeat it. - if (sign == SimdSign::Unsigned) - sign = SimdSign::NotApplicable; - } - } - - // We have the right low 8 bits in |output|, but we may need to fix the high - // bits. Note that this requires |output| to be one of the %eax-%edx - // registers. - switch (sign) { - case SimdSign::Signed: - movsbl(output, output); - break; - case SimdSign::Unsigned: - movzbl(output, output); - break; - case SimdSign::NotApplicable: - // No adjustment needed. - break; - } -} - -void -MacroAssemblerX86Shared::extractLaneSimdBool(FloatRegister input, Register output, unsigned numLanes, - unsigned lane) -{ - switch (numLanes) { - case 4: - extractLaneInt32x4(input, output, lane); - break; - case 8: - // Get a lane, don't bother fixing the high bits since we'll mask below. - extractLaneInt16x8(input, output, lane, SimdSign::NotApplicable); - break; - case 16: - extractLaneInt8x16(input, output, lane, SimdSign::NotApplicable); - break; - default: - MOZ_CRASH("Unhandled SIMD number of lanes"); - } - // We need to generate a 0/1 value. We have 0/-1 and possibly dirty high bits. - asMasm().and32(Imm32(1), output); -} - -void -MacroAssemblerX86Shared::insertLaneSimdInt(FloatRegister input, Register value, FloatRegister output, - unsigned lane, unsigned numLanes) -{ - if (numLanes == 8) { - // Available in SSE 2. - vpinsrw(lane, value, input, output); - return; - } - - // Note that, contrarily to float32x4, we cannot use vmovd if the inserted - // value goes into the first component, as vmovd clears out the higher lanes - // of the output. - if (AssemblerX86Shared::HasSSE41()) { - // TODO: Teach Lowering that we don't need defineReuseInput if we have AVX. - switch (numLanes) { - case 4: - vpinsrd(lane, value, input, output); - return; - case 16: - vpinsrb(lane, value, input, output); - return; - } - } - - asMasm().reserveStack(Simd128DataSize); - storeAlignedSimd128Int(input, Address(StackPointer, 0)); - switch (numLanes) { - case 4: - store32(value, Address(StackPointer, lane * sizeof(int32_t))); - break; - case 16: - // Note that this requires `value` to be in one the registers where the - // low 8 bits are addressible (%eax - %edx on x86, all of them on x86-64). - store8(value, Address(StackPointer, lane * sizeof(int8_t))); - break; - default: - MOZ_CRASH("Unsupported SIMD numLanes"); - } - loadAlignedSimd128Int(Address(StackPointer, 0), output); - asMasm().freeStack(Simd128DataSize); -} - -void -MacroAssemblerX86Shared::insertLaneFloat32x4(FloatRegister input, FloatRegister value, - FloatRegister output, unsigned lane) -{ - if (lane == 0) { - // As both operands are registers, vmovss doesn't modify the upper bits - // of the destination operand. - if (value != output) - vmovss(value, input, output); - return; - } - - if (AssemblerX86Shared::HasSSE41()) { - // The input value is in the low float32 of the 'value' FloatRegister. - vinsertps(vinsertpsMask(0, lane), value, output, output); - return; - } - - asMasm().reserveStack(Simd128DataSize); - storeAlignedSimd128Float(input, Address(StackPointer, 0)); - asMasm().storeFloat32(value, Address(StackPointer, lane * sizeof(int32_t))); - loadAlignedSimd128Float(Address(StackPointer, 0), output); - asMasm().freeStack(Simd128DataSize); -} - -void -MacroAssemblerX86Shared::allTrueSimdBool(FloatRegister input, Register output) -{ - // We know that the input lanes are boolean, so they are either 0 or -1. - // The all-true vector has all 128 bits set, no matter the lane geometry. - vpmovmskb(input, output); - cmp32(output, Imm32(0xffff)); - emitSet(Assembler::Zero, output); -} - -void -MacroAssemblerX86Shared::anyTrueSimdBool(FloatRegister input, Register output) -{ - vpmovmskb(input, output); - cmp32(output, Imm32(0x0)); - emitSet(Assembler::NonZero, output); -} - -void -MacroAssemblerX86Shared::swizzleInt32x4(FloatRegister input, FloatRegister output, - unsigned lanes[4]) -{ - uint32_t mask = MacroAssembler::ComputeShuffleMask(lanes[0], lanes[1], lanes[2], lanes[3]); - shuffleInt32(mask, input, output); -} - -void -MacroAssemblerX86Shared::swizzleInt8x16(FloatRegister input, FloatRegister output, - const Maybe& temp, int8_t lanes[16]) -{ - if (AssemblerX86Shared::HasSSSE3()) { - ScratchSimd128Scope scratch(asMasm()); - asMasm().loadConstantSimd128Int(SimdConstant::CreateX16(lanes), scratch); - FloatRegister inputCopy = reusedInputInt32x4(input, output); - vpshufb(scratch, inputCopy, output); - return; - } - - // Worst-case fallback for pre-SSSE3 machines. Bounce through memory. - MOZ_ASSERT(!!temp, "needs a temp for the memory fallback"); - asMasm().reserveStack(2 * Simd128DataSize); - storeAlignedSimd128Int(input, Address(StackPointer, Simd128DataSize)); - for (unsigned i = 0; i < 16; i++) { - load8ZeroExtend(Address(StackPointer, Simd128DataSize + lanes[i]), *temp); - store8(*temp, Address(StackPointer, i)); - } - loadAlignedSimd128Int(Address(StackPointer, 0), output); - asMasm().freeStack(2 * Simd128DataSize); -} - -static inline bool -LanesMatch(unsigned lanes[4], unsigned x, unsigned y, unsigned z, unsigned w) -{ - return lanes[0] == x && lanes[1] == y && lanes[2] == z && lanes[3] == w; -} - -void -MacroAssemblerX86Shared::swizzleFloat32x4(FloatRegister input, FloatRegister output, - unsigned lanes[4]) -{ - if (AssemblerX86Shared::HasSSE3()) { - if (LanesMatch(lanes, 0, 0, 2, 2)) { - vmovsldup(input, output); - return; - } - if (LanesMatch(lanes, 1, 1, 3, 3)) { - vmovshdup(input, output); - return; - } - } - - // TODO Here and below, arch specific lowering could identify this pattern - // and use defineReuseInput to avoid this move (bug 1084404) - if (LanesMatch(lanes, 2, 3, 2, 3)) { - FloatRegister inputCopy = reusedInputFloat32x4(input, output); - vmovhlps(input, inputCopy, output); - return; - } - - if (LanesMatch(lanes, 0, 1, 0, 1)) { - if (AssemblerX86Shared::HasSSE3() && !AssemblerX86Shared::HasAVX()) { - vmovddup(input, output); - return; - } - FloatRegister inputCopy = reusedInputFloat32x4(input, output); - vmovlhps(input, inputCopy, output); - return; - } - - if (LanesMatch(lanes, 0, 0, 1, 1)) { - FloatRegister inputCopy = reusedInputFloat32x4(input, output); - vunpcklps(input, inputCopy, output); - return; - } - - if (LanesMatch(lanes, 2, 2, 3, 3)) { - FloatRegister inputCopy = reusedInputFloat32x4(input, output); - vunpckhps(input, inputCopy, output); - return; - } - - uint32_t x = lanes[0]; - uint32_t y = lanes[1]; - uint32_t z = lanes[2]; - uint32_t w = lanes[3]; - - uint32_t mask = MacroAssembler::ComputeShuffleMask(x, y, z, w); - shuffleFloat32(mask, input, output); -} - -void -MacroAssemblerX86Shared::shuffleInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister output, - const Maybe& maybeFloatTemp, - const Maybe& maybeTemp, uint8_t lanes[16]) -{ - DebugOnly hasSSSE3 = AssemblerX86Shared::HasSSSE3(); - MOZ_ASSERT(hasSSSE3 == !!maybeFloatTemp); - MOZ_ASSERT(!hasSSSE3 == !!maybeTemp); - - // Use pshufb if it is available. - if (AssemblerX86Shared::HasSSSE3()) { - ScratchSimd128Scope scratch(asMasm()); - - // Use pshufb instructions to gather the lanes from each source vector. - // A negative index creates a zero lane, so the two vectors can be combined. - - // Set scratch = lanes from lhs. - int8_t idx[16]; - for (unsigned i = 0; i < 16; i++) - idx[i] = lanes[i] < 16 ? lanes[i] : -1; - asMasm().loadConstantSimd128Int(SimdConstant::CreateX16(idx), *maybeFloatTemp); - FloatRegister lhsCopy = reusedInputInt32x4(lhs, scratch); - vpshufb(*maybeFloatTemp, lhsCopy, scratch); - - // Set output = lanes from rhs. - for (unsigned i = 0; i < 16; i++) - idx[i] = lanes[i] >= 16 ? lanes[i] - 16 : -1; - asMasm().loadConstantSimd128Int(SimdConstant::CreateX16(idx), *maybeFloatTemp); - FloatRegister rhsCopy = reusedInputInt32x4(rhs, output); - vpshufb(*maybeFloatTemp, rhsCopy, output); - - // Combine. - vpor(scratch, output, output); - return; - } - - // Worst-case fallback for pre-SSE3 machines. Bounce through memory. - asMasm().reserveStack(3 * Simd128DataSize); - storeAlignedSimd128Int(lhs, Address(StackPointer, Simd128DataSize)); - storeAlignedSimd128Int(rhs, Address(StackPointer, 2 * Simd128DataSize)); - for (unsigned i = 0; i < 16; i++) { - load8ZeroExtend(Address(StackPointer, Simd128DataSize + lanes[i]), *maybeTemp); - store8(*maybeTemp, Address(StackPointer, i)); - } - loadAlignedSimd128Int(Address(StackPointer, 0), output); - asMasm().freeStack(3 * Simd128DataSize); -} - -void -MacroAssemblerX86Shared::shuffleX4(FloatRegister lhs, Operand rhs, FloatRegister out, - const Maybe& maybeTemp, unsigned lanes[4]) -{ - uint32_t x = lanes[0]; - uint32_t y = lanes[1]; - uint32_t z = lanes[2]; - uint32_t w = lanes[3]; - - // Check that lanes come from LHS in majority: - unsigned numLanesFromLHS = (x < 4) + (y < 4) + (z < 4) + (w < 4); - MOZ_ASSERT(numLanesFromLHS >= 2); - - // When reading this method, remember that vshufps takes the two first - // inputs of the destination operand (right operand) and the two last - // inputs of the source operand (left operand). - // - // Legend for explanations: - // - L: LHS - // - R: RHS - // - T: temporary - - uint32_t mask; - - // If all lanes came from a single vector, we should use swizzle instead. - MOZ_ASSERT(numLanesFromLHS < 4); - - // If all values stay in their lane, this is a blend. - if (AssemblerX86Shared::HasSSE41()) { - if (x % 4 == 0 && y % 4 == 1 && z % 4 == 2 && w % 4 == 3) { - vblendps(blendpsMask(x >= 4, y >= 4, z >= 4, w >= 4), rhs, lhs, out); - return; - } - } - - // One element of the second, all other elements of the first - if (numLanesFromLHS == 3) { - unsigned firstMask = -1, secondMask = -1; - - // register-register vmovss preserves the high lanes. - if (LanesMatch(lanes, 4, 1, 2, 3) && rhs.kind() == Operand::FPREG) { - vmovss(FloatRegister::FromCode(rhs.fpu()), lhs, out); - return; - } - - // SSE4.1 vinsertps can handle any single element. - unsigned numLanesUnchanged = (x == 0) + (y == 1) + (z == 2) + (w == 3); - if (AssemblerX86Shared::HasSSE41() && numLanesUnchanged == 3) { - unsigned srcLane; - unsigned dstLane; - if (x >= 4) { - srcLane = x - 4; - dstLane = 0; - } else if (y >= 4) { - srcLane = y - 4; - dstLane = 1; - } else if (z >= 4) { - srcLane = z - 4; - dstLane = 2; - } else { - MOZ_ASSERT(w >= 4); - srcLane = w - 4; - dstLane = 3; - } - vinsertps(vinsertpsMask(srcLane, dstLane), rhs, lhs, out); - return; - } - - MOZ_ASSERT(!!maybeTemp); - FloatRegister rhsCopy = *maybeTemp; - loadAlignedSimd128Float(rhs, rhsCopy); - - if (x < 4 && y < 4) { - if (w >= 4) { - w %= 4; - // T = (Rw Rw Lz Lz) = vshufps(firstMask, lhs, rhs, rhsCopy) - firstMask = MacroAssembler::ComputeShuffleMask(w, w, z, z); - // (Lx Ly Lz Rw) = (Lx Ly Tz Tx) = vshufps(secondMask, T, lhs, out) - secondMask = MacroAssembler::ComputeShuffleMask(x, y, 2, 0); - } else { - MOZ_ASSERT(z >= 4); - z %= 4; - // T = (Rz Rz Lw Lw) = vshufps(firstMask, lhs, rhs, rhsCopy) - firstMask = MacroAssembler::ComputeShuffleMask(z, z, w, w); - // (Lx Ly Rz Lw) = (Lx Ly Tx Tz) = vshufps(secondMask, T, lhs, out) - secondMask = MacroAssembler::ComputeShuffleMask(x, y, 0, 2); - } - - vshufps(firstMask, lhs, rhsCopy, rhsCopy); - vshufps(secondMask, rhsCopy, lhs, out); - return; - } - - MOZ_ASSERT(z < 4 && w < 4); - - if (y >= 4) { - y %= 4; - // T = (Ry Ry Lx Lx) = vshufps(firstMask, lhs, rhs, rhsCopy) - firstMask = MacroAssembler::ComputeShuffleMask(y, y, x, x); - // (Lx Ry Lz Lw) = (Tz Tx Lz Lw) = vshufps(secondMask, lhs, T, out) - secondMask = MacroAssembler::ComputeShuffleMask(2, 0, z, w); - } else { - MOZ_ASSERT(x >= 4); - x %= 4; - // T = (Rx Rx Ly Ly) = vshufps(firstMask, lhs, rhs, rhsCopy) - firstMask = MacroAssembler::ComputeShuffleMask(x, x, y, y); - // (Rx Ly Lz Lw) = (Tx Tz Lz Lw) = vshufps(secondMask, lhs, T, out) - secondMask = MacroAssembler::ComputeShuffleMask(0, 2, z, w); - } - - vshufps(firstMask, lhs, rhsCopy, rhsCopy); - if (AssemblerX86Shared::HasAVX()) { - vshufps(secondMask, lhs, rhsCopy, out); - } else { - vshufps(secondMask, lhs, rhsCopy, rhsCopy); - moveSimd128Float(rhsCopy, out); - } - return; - } - - // Two elements from one vector, two other elements from the other - MOZ_ASSERT(numLanesFromLHS == 2); - - // TODO Here and below, symmetric case would be more handy to avoid a move, - // but can't be reached because operands would get swapped (bug 1084404). - if (LanesMatch(lanes, 2, 3, 6, 7)) { - ScratchSimd128Scope scratch(asMasm()); - if (AssemblerX86Shared::HasAVX()) { - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, scratch); - vmovhlps(lhs, rhsCopy, out); - } else { - loadAlignedSimd128Float(rhs, scratch); - vmovhlps(lhs, scratch, scratch); - moveSimd128Float(scratch, out); - } - return; - } - - if (LanesMatch(lanes, 0, 1, 4, 5)) { - FloatRegister rhsCopy; - ScratchSimd128Scope scratch(asMasm()); - if (rhs.kind() == Operand::FPREG) { - // No need to make an actual copy, since the operand is already - // in a register, and it won't be clobbered by the vmovlhps. - rhsCopy = FloatRegister::FromCode(rhs.fpu()); - } else { - loadAlignedSimd128Float(rhs, scratch); - rhsCopy = scratch; - } - vmovlhps(rhsCopy, lhs, out); - return; - } - - if (LanesMatch(lanes, 0, 4, 1, 5)) { - vunpcklps(rhs, lhs, out); - return; - } - - // TODO swapped case would be better (bug 1084404) - if (LanesMatch(lanes, 4, 0, 5, 1)) { - ScratchSimd128Scope scratch(asMasm()); - if (AssemblerX86Shared::HasAVX()) { - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, scratch); - vunpcklps(lhs, rhsCopy, out); - } else { - loadAlignedSimd128Float(rhs, scratch); - vunpcklps(lhs, scratch, scratch); - moveSimd128Float(scratch, out); - } - return; - } - - if (LanesMatch(lanes, 2, 6, 3, 7)) { - vunpckhps(rhs, lhs, out); - return; - } - - // TODO swapped case would be better (bug 1084404) - if (LanesMatch(lanes, 6, 2, 7, 3)) { - ScratchSimd128Scope scratch(asMasm()); - if (AssemblerX86Shared::HasAVX()) { - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, scratch); - vunpckhps(lhs, rhsCopy, out); - } else { - loadAlignedSimd128Float(rhs, scratch); - vunpckhps(lhs, scratch, scratch); - moveSimd128Float(scratch, out); - } - return; - } - - // In one vshufps - if (x < 4 && y < 4) { - mask = MacroAssembler::ComputeShuffleMask(x, y, z % 4, w % 4); - vshufps(mask, rhs, lhs, out); - return; - } - - // At creation, we should have explicitly swapped in this case. - MOZ_ASSERT(!(z >= 4 && w >= 4)); - - // In two vshufps, for the most generic case: - uint32_t firstMask[4], secondMask[4]; - unsigned i = 0, j = 2, k = 0; - -#define COMPUTE_MASK(lane) \ - if (lane >= 4) { \ - firstMask[j] = lane % 4; \ - secondMask[k++] = j++; \ - } else { \ - firstMask[i] = lane; \ - secondMask[k++] = i++; \ - } - - COMPUTE_MASK(x) - COMPUTE_MASK(y) - COMPUTE_MASK(z) - COMPUTE_MASK(w) -#undef COMPUTE_MASK - - MOZ_ASSERT(i == 2 && j == 4 && k == 4); - - mask = MacroAssembler::ComputeShuffleMask(firstMask[0], firstMask[1], - firstMask[2], firstMask[3]); - vshufps(mask, rhs, lhs, lhs); - - mask = MacroAssembler::ComputeShuffleMask(secondMask[0], secondMask[1], - secondMask[2], secondMask[3]); - vshufps(mask, lhs, lhs, lhs); -} - -static inline FloatRegister -ToSimdFloatRegister(const Operand& op) -{ - return FloatRegister(op.fpu(), FloatRegister::Codes::ContentType::Simd128); -} - -void -MacroAssemblerX86Shared::compareInt8x16(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output) -{ - static const SimdConstant allOnes = SimdConstant::SplatX16(-1); - ScratchSimd128Scope scratch(asMasm()); - switch (cond) { - case Assembler::Condition::GreaterThan: - vpcmpgtb(rhs, lhs, output); - break; - case Assembler::Condition::Equal: - vpcmpeqb(rhs, lhs, output); - break; - case Assembler::Condition::LessThan: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - - // src := src > lhs (i.e. lhs < rhs) - // Improve by doing custom lowering (rhs is tied to the output register) - vpcmpgtb(Operand(lhs), scratch, scratch); - moveSimd128Int(scratch, output); - break; - case Assembler::Condition::NotEqual: - // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we - // should invert the comparison by, e.g. swapping the arms of a select - // if that's what it's used in. - asMasm().loadConstantSimd128Int(allOnes, scratch); - vpcmpeqb(rhs, lhs, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - case Assembler::Condition::GreaterThanOrEqual: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - vpcmpgtb(Operand(lhs), scratch, scratch); - asMasm().loadConstantSimd128Int(allOnes, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - case Assembler::Condition::LessThanOrEqual: - // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. - asMasm().loadConstantSimd128Int(allOnes, scratch); - vpcmpgtb(rhs, lhs, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - default: - MOZ_CRASH("unexpected condition op"); - } -} - -void -MacroAssemblerX86Shared::compareInt16x8(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output) -{ - static const SimdConstant allOnes = SimdConstant::SplatX8(-1); - - ScratchSimd128Scope scratch(asMasm()); - switch (cond) { - case Assembler::Condition::GreaterThan: - vpcmpgtw(rhs, lhs, output); - break; - case Assembler::Condition::Equal: - vpcmpeqw(rhs, lhs, output); - break; - case Assembler::Condition::LessThan: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - - // src := src > lhs (i.e. lhs < rhs) - // Improve by doing custom lowering (rhs is tied to the output register) - vpcmpgtw(Operand(lhs), scratch, scratch); - moveSimd128Int(scratch, output); - break; - case Assembler::Condition::NotEqual: - // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we - // should invert the comparison by, e.g. swapping the arms of a select - // if that's what it's used in. - asMasm().loadConstantSimd128Int(allOnes, scratch); - vpcmpeqw(rhs, lhs, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - case Assembler::Condition::GreaterThanOrEqual: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - vpcmpgtw(Operand(lhs), scratch, scratch); - asMasm().loadConstantSimd128Int(allOnes, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - case Assembler::Condition::LessThanOrEqual: - // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. - asMasm().loadConstantSimd128Int(allOnes, scratch); - vpcmpgtw(rhs, lhs, output); - bitwiseXorSimdInt(output, Operand(scratch), output); - break; - default: - MOZ_CRASH("unexpected condition op"); - } -} - -void -MacroAssemblerX86Shared::compareInt32x4(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output) -{ - static const SimdConstant allOnes = SimdConstant::SplatX4(-1); - ScratchSimd128Scope scratch(asMasm()); - switch (cond) { - case Assembler::Condition::GreaterThan: - packedGreaterThanInt32x4(rhs, lhs); - break; - case Assembler::Condition::Equal: - packedEqualInt32x4(rhs, lhs); - break; - case Assembler::Condition::LessThan: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - - // src := src > lhs (i.e. lhs < rhs) - // Improve by doing custom lowering (rhs is tied to the output register) - packedGreaterThanInt32x4(Operand(lhs), scratch); - moveSimd128Int(scratch, lhs); - break; - case Assembler::Condition::NotEqual: - // Ideally for notEqual, greaterThanOrEqual, and lessThanOrEqual, we - // should invert the comparison by, e.g. swapping the arms of a select - // if that's what it's used in. - asMasm().loadConstantSimd128Int(allOnes, scratch); - packedEqualInt32x4(rhs, lhs); - bitwiseXorSimdInt(lhs, Operand(scratch), lhs); - break; - case Assembler::Condition::GreaterThanOrEqual: - // src := rhs - if (rhs.kind() == Operand::FPREG) - moveSimd128Int(ToSimdFloatRegister(rhs), scratch); - else - loadAlignedSimd128Int(rhs, scratch); - packedGreaterThanInt32x4(Operand(lhs), scratch); - asMasm().loadConstantSimd128Int(allOnes, lhs); - bitwiseXorSimdInt(lhs, Operand(scratch), lhs); - break; - case Assembler::Condition::LessThanOrEqual: - // lhs <= rhs is equivalent to !(rhs < lhs), which we compute here. - asMasm().loadConstantSimd128Int(allOnes, scratch); - packedGreaterThanInt32x4(rhs, lhs); - bitwiseXorSimdInt(lhs, Operand(scratch), lhs); - break; - default: - MOZ_CRASH("unexpected condition op"); - } -} - -void -MacroAssemblerX86Shared::compareFloat32x4(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output) -{ - switch (cond) { - case Assembler::Condition::Equal: - vcmpeqps(rhs, lhs, output); - break; - case Assembler::Condition::LessThan: - vcmpltps(rhs, lhs, output); - break; - case Assembler::Condition::LessThanOrEqual: - vcmpleps(rhs, lhs, output); - break; - case Assembler::Condition::NotEqual: - vcmpneqps(rhs, lhs, output); - break; - case Assembler::Condition::GreaterThanOrEqual: - case Assembler::Condition::GreaterThan: - // We reverse these before register allocation so that we don't have to - // copy into and out of temporaries after codegen. - MOZ_CRASH("should have reversed this"); - default: - MOZ_CRASH("unexpected condition op"); - } -} - -void -MacroAssemblerX86Shared::mulInt32x4(FloatRegister lhs, Operand rhs, - const Maybe& temp, FloatRegister output) -{ - if (AssemblerX86Shared::HasSSE41()) { - vpmulld(rhs, lhs, output); - return; - } - - ScratchSimd128Scope scratch(asMasm()); - loadAlignedSimd128Int(rhs, scratch); - vpmuludq(lhs, scratch, scratch); - // scratch contains (Rx, _, Rz, _) where R is the resulting vector. - - MOZ_ASSERT(!!temp); - vpshufd(MacroAssembler::ComputeShuffleMask(1, 1, 3, 3), lhs, lhs); - vpshufd(MacroAssembler::ComputeShuffleMask(1, 1, 3, 3), rhs, *temp); - vpmuludq(*temp, lhs, lhs); - // lhs contains (Ry, _, Rw, _) where R is the resulting vector. - - vshufps(MacroAssembler::ComputeShuffleMask(0, 2, 0, 2), scratch, lhs, lhs); - // lhs contains (Ry, Rw, Rx, Rz) - vshufps(MacroAssembler::ComputeShuffleMask(2, 0, 3, 1), lhs, lhs, lhs); -} - -void -MacroAssemblerX86Shared::minFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output) -{ - ScratchSimd128Scope scratch(asMasm()); - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, scratch); - vminps(Operand(lhs), rhsCopy, scratch); - vminps(rhs, lhs, output); - vorps(scratch, output, output); // NaN or'd with arbitrary bits is NaN -} - -void -MacroAssemblerX86Shared::maxFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, - FloatRegister output) -{ - ScratchSimd128Scope scratch(asMasm()); - FloatRegister lhsCopy = reusedInputFloat32x4(lhs, scratch); - vcmpunordps(rhs, lhsCopy, scratch); - - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, temp); - vmaxps(Operand(lhs), rhsCopy, temp); - vmaxps(rhs, lhs, output); - - vandps(temp, output, output); - vorps(scratch, output, output); // or in the all-ones NaNs -} - -void -MacroAssemblerX86Shared::minNumFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, - FloatRegister output) -{ - ScratchSimd128Scope scratch(asMasm()); - asMasm().loadConstantSimd128Int(SimdConstant::SplatX4(int32_t(0x80000000)), temp); - - FloatRegister mask = scratch; - FloatRegister tmpCopy = reusedInputFloat32x4(temp, scratch); - vpcmpeqd(Operand(lhs), tmpCopy, mask); - vandps(temp, mask, mask); - - FloatRegister lhsCopy = reusedInputFloat32x4(lhs, temp); - vminps(rhs, lhsCopy, temp); - vorps(mask, temp, temp); - - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, mask); - vcmpneqps(rhs, rhsCopy, mask); - - if (AssemblerX86Shared::HasAVX()) { - vblendvps(mask, lhs, temp, output); - } else { - // Emulate vblendvps. - // With SSE.4.1 we could use blendvps, however it's awkward since - // it requires the mask to be in xmm0. - if (lhs != output) - moveSimd128Float(lhs, output); - vandps(Operand(mask), output, output); - vandnps(Operand(temp), mask, mask); - vorps(Operand(mask), output, output); - } -} - -void -MacroAssemblerX86Shared::maxNumFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, - FloatRegister output) -{ - ScratchSimd128Scope scratch(asMasm()); - FloatRegister mask = scratch; - - asMasm().loadConstantSimd128Int(SimdConstant::SplatX4(0), mask); - vpcmpeqd(Operand(lhs), mask, mask); - - asMasm().loadConstantSimd128Int(SimdConstant::SplatX4(int32_t(0x80000000)), temp); - vandps(temp, mask, mask); - - FloatRegister lhsCopy = reusedInputFloat32x4(lhs, temp); - vmaxps(rhs, lhsCopy, temp); - vandnps(Operand(temp), mask, mask); - - // Ensure temp always contains the temporary result - mask = temp; - temp = scratch; - - FloatRegister rhsCopy = reusedInputAlignedFloat32x4(rhs, mask); - vcmpneqps(rhs, rhsCopy, mask); - - if (AssemblerX86Shared::HasAVX()) { - vblendvps(mask, lhs, temp, output); - } else { - // Emulate vblendvps. - // With SSE.4.1 we could use blendvps, however it's awkward since - // it requires the mask to be in xmm0. - if (lhs != output) - moveSimd128Float(lhs, output); - vandps(Operand(mask), output, output); - vandnps(Operand(temp), mask, mask); - vorps(Operand(mask), output, output); - } -} - -void -MacroAssemblerX86Shared::negFloat32x4(Operand in, FloatRegister out) -{ - // All zeros but the sign bit - static const SimdConstant minusZero = SimdConstant::SplatX4(-0.f); - asMasm().loadConstantSimd128Float(minusZero, out); - bitwiseXorFloat32x4(out, in, out); -} - -void -MacroAssemblerX86Shared::notInt8x16(Operand in, FloatRegister out) -{ - static const SimdConstant allOnes = SimdConstant::SplatX16(-1); - asMasm().loadConstantSimd128Int(allOnes, out); - bitwiseXorSimdInt(out, in, out); -} - -void -MacroAssemblerX86Shared::notInt16x8(Operand in, FloatRegister out) -{ - static const SimdConstant allOnes = SimdConstant::SplatX8(-1); - asMasm().loadConstantSimd128Int(allOnes, out); - bitwiseXorSimdInt(out, in, out); -} - -void -MacroAssemblerX86Shared::notInt32x4(Operand in, FloatRegister out) -{ - static const SimdConstant allOnes = SimdConstant::SplatX4(-1); - asMasm().loadConstantSimd128Int(allOnes, out); - bitwiseXorSimdInt(out, in, out); -} - -void -MacroAssemblerX86Shared::notFloat32x4(Operand in, FloatRegister out) -{ - float ones = SpecificNaN(1, FloatingPoint::kSignificandBits); - static const SimdConstant allOnes = SimdConstant::SplatX4(ones); - asMasm().loadConstantSimd128Float(allOnes, out); - bitwiseXorFloat32x4(out, in, out); -} - -void -MacroAssemblerX86Shared::absFloat32x4(Operand in, FloatRegister out) -{ - // All ones but the sign bit - float signMask = SpecificNaN(0, FloatingPoint::kSignificandBits); - static const SimdConstant signMasks = SimdConstant::SplatX4(signMask); - asMasm().loadConstantSimd128Float(signMasks, out); - bitwiseAndFloat32x4(out, in, out); -} - -static inline void -MaskSimdShiftCount(MacroAssembler& masm, unsigned shiftmask, Register count, Register temp, - FloatRegister dest) -{ - masm.mov(count, temp); - masm.andl(Imm32(shiftmask), temp); - masm.vmovd(temp, dest); -} - -void -MacroAssemblerX86Shared::packedLeftShiftByScalarInt16x8(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 15, count, temp, scratch); - vpsllw(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::packedRightShiftByScalarInt16x8(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 15, count, temp, scratch); - vpsraw(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::packedUnsignedRightShiftByScalarInt16x8(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 15, count, temp, scratch); - vpsrlw(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::packedLeftShiftByScalarInt32x4(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 31, count, temp, scratch); - vpslld(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::packedRightShiftByScalarInt32x4(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 31, count, temp, scratch); - vpsrad(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::packedUnsignedRightShiftByScalarInt32x4(FloatRegister in, Register count, - Register temp, FloatRegister dest) -{ - ScratchSimd128Scope scratch(asMasm()); - MaskSimdShiftCount(asMasm(), 31, count, temp, scratch); - vpsrld(scratch, in, dest); -} - -void -MacroAssemblerX86Shared::selectSimd128(FloatRegister mask, FloatRegister onTrue, FloatRegister onFalse, - FloatRegister temp, FloatRegister output) -{ - if (onTrue != output) - vmovaps(onTrue, output); - if (mask != temp) - vmovaps(mask, temp); - - // SSE4.1 has plain blendvps which can do this, but it is awkward - // to use because it requires the mask to be in xmm0. - - bitwiseAndSimdInt(output, Operand(temp), output); - bitwiseAndNotSimdInt(temp, Operand(onFalse), temp); - bitwiseOrSimdInt(output, Operand(temp), output); -} diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h index 2ff4b748bdb9..b3df50125867 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h @@ -1109,6 +1109,29 @@ MacroAssembler::spectreZeroRegister(Condition cond, Register scratch, Register d spectreMovePtr(cond, scratch, dest); } +// ======================================================================== +// Canonicalization primitives. +void +MacroAssembler::canonicalizeFloat32x4(FloatRegister reg, FloatRegister scratch) +{ + ScratchSimd128Scope scratch2(*this); + + MOZ_ASSERT(scratch.asSimd128() != scratch2.asSimd128()); + MOZ_ASSERT(reg.asSimd128() != scratch2.asSimd128()); + MOZ_ASSERT(reg.asSimd128() != scratch.asSimd128()); + + FloatRegister mask = scratch; + vcmpordps(Operand(reg), reg, mask); + + FloatRegister ifFalse = scratch2; + float nanf = float(JS::GenericNaN()); + loadConstantSimd128Float(SimdConstant::SplatX4(nanf), ifFalse); + + bitwiseAndSimd128(Operand(mask), reg); + bitwiseAndNotSimd128(Operand(ifFalse), mask); + bitwiseOrSimd128(Operand(mask), reg); +} + // ======================================================================== // Memory access primitives. void diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h index 9de4b2f9f9b5..586301618a76 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -106,8 +106,7 @@ class MacroAssemblerX86Shared : public Assembler vucomiss(rhs, lhs); } - void branchNegativeZero(FloatRegister reg, Register scratch, Label* label, - bool maybeNonZero = true); + void branchNegativeZero(FloatRegister reg, Register scratch, Label* label, bool maybeNonZero = true); void branchNegativeZeroFloat32(FloatRegister reg, Register scratch, Label* label); void move32(Imm32 imm, Register dest) { @@ -411,179 +410,20 @@ class MacroAssemblerX86Shared : public Assembler vcvtdq2ps(src, dest); } - // SIMD methods, defined in MacroAssembler-x86-shared-SIMD.cpp. - void checkedConvertFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest, Register temp, - Label* oolCheck, Label* rejoin); - void oolConvertFloat32x4ToInt32x4(FloatRegister src, Register temp, Label* rejoin, - Label* onConversionError); - void checkedConvertFloat32x4ToUint32x4(FloatRegister src, FloatRegister dest, Register temp, - FloatRegister tempF, Label* failed); - - void createInt32x4(Register lane0, Register lane1, Register lane2, Register lane3, - FloatRegister dest); - void createFloat32x4(FloatRegister lane0, FloatRegister lane1, FloatRegister lane2, - FloatRegister lane3, FloatRegister temp, FloatRegister output); - - void splatX16(Register input, FloatRegister output); - void splatX8(Register input, FloatRegister output); - void splatX4(Register input, FloatRegister output); - void splatX4(FloatRegister input, FloatRegister output); - - void reinterpretSimd(bool isIntegerLaneType, FloatRegister input, FloatRegister output); - - void extractLaneInt32x4(FloatRegister input, Register output, unsigned lane); - void extractLaneFloat32x4(FloatRegister input, FloatRegister output, unsigned lane, - bool canonicalize); - void extractLaneInt16x8(FloatRegister input, Register output, unsigned lane, SimdSign sign); - void extractLaneInt8x16(FloatRegister input, Register output, unsigned lane, SimdSign sign); - void extractLaneSimdBool(FloatRegister input, Register output, unsigned numLanes, unsigned lane); - - void insertLaneSimdInt(FloatRegister input, Register value, FloatRegister output, - unsigned lane, unsigned numLanes); - void insertLaneFloat32x4(FloatRegister input, FloatRegister value, FloatRegister output, - unsigned lane); - - void allTrueSimdBool(FloatRegister input, Register output); - void anyTrueSimdBool(FloatRegister input, Register output); - - void swizzleInt32x4(FloatRegister input, FloatRegister output, unsigned lanes[4]); - void swizzleFloat32x4(FloatRegister input, FloatRegister output, unsigned lanes[4]); - void swizzleInt8x16(FloatRegister input, FloatRegister output, - const mozilla::Maybe& temp, int8_t lanes[16]); - - void shuffleX4(FloatRegister lhs, Operand rhs, FloatRegister out, - const mozilla::Maybe& maybeTemp, unsigned lanes[4]); - void shuffleInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister output, - const mozilla::Maybe& maybeFloatTemp, - const mozilla::Maybe& maybeTemp, uint8_t lanes[16]); - - void compareInt8x16(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output); - void compareInt16x8(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output); - void compareInt32x4(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output); - void compareFloat32x4(FloatRegister lhs, Operand rhs, Assembler::Condition cond, - FloatRegister output); - - void addInt8x16(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpaddb(rhs, lhs, output); + void bitwiseAndSimd128(const Operand& src, FloatRegister dest) { + // TODO Using the "ps" variant for all types incurs a domain crossing + // penalty for integer types and double. + vandps(src, dest, dest); } - void addInt16x8(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpaddw(rhs, lhs, output); + void bitwiseAndNotSimd128(const Operand& src, FloatRegister dest) { + vandnps(src, dest, dest); } - void addInt32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpaddd(rhs, lhs, output); + void bitwiseOrSimd128(const Operand& src, FloatRegister dest) { + vorps(src, dest, dest); } - void addFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vaddps(rhs, lhs, output); + void bitwiseXorSimd128(const Operand& src, FloatRegister dest) { + vxorps(src, dest, dest); } - - void addSatInt8x16(FloatRegister lhs, Operand rhs, SimdSign sign, FloatRegister output) { - if (sign == SimdSign::Signed) - vpaddsb(rhs, lhs, output); - else - vpaddusb(rhs, lhs, output); - } - void addSatInt16x8(FloatRegister lhs, Operand rhs, SimdSign sign, FloatRegister output) { - if (sign == SimdSign::Signed) - vpaddsw(rhs, lhs, output); - else - vpaddusw(rhs, lhs, output); - } - - void subInt8x16(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpsubb(rhs, lhs, output); - } - void subInt16x8(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpsubw(rhs, lhs, output); - } - void subInt32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpsubd(rhs, lhs, output); - } - void subFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vsubps(rhs, lhs, output); - } - - void subSatInt8x16(FloatRegister lhs, Operand rhs, SimdSign sign, FloatRegister output) { - if (sign == SimdSign::Signed) - vpsubsb(rhs, lhs, output); - else - vpsubusb(rhs, lhs, output); - } - void subSatInt16x8(FloatRegister lhs, Operand rhs, SimdSign sign, FloatRegister output) { - if (sign == SimdSign::Signed) - vpsubsw(rhs, lhs, output); - else - vpsubusw(rhs, lhs, output); - } - - void mulInt16x8(FloatRegister lhs, Operand rhs, FloatRegister output) { - vpmullw(rhs, lhs, output); - } - void mulInt32x4(FloatRegister lhs, Operand rhs, const mozilla::Maybe& temp, - FloatRegister output); - void mulFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vmulps(rhs, lhs, output); - } - - void negInt8x16(Operand in, FloatRegister out) { - zeroSimd128Int(out); - packedSubInt8(in, out); - } - void negInt16x8(Operand in, FloatRegister out) { - zeroSimd128Int(out); - packedSubInt16(in, out); - } - void negInt32x4(Operand in, FloatRegister out) { - zeroSimd128Int(out); - packedSubInt32(in, out); - } - void negFloat32x4(Operand in, FloatRegister out); - - void notInt8x16(Operand in, FloatRegister out); - void notInt16x8(Operand in, FloatRegister out); - void notInt32x4(Operand in, FloatRegister out); - void notFloat32x4(Operand in, FloatRegister out); - - void divFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output) { - vdivps(rhs, lhs, output); - } - void minFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister output); - void maxFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, FloatRegister output); - void minNumFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, FloatRegister output); - void maxNumFloat32x4(FloatRegister lhs, Operand rhs, FloatRegister temp, FloatRegister output); - - void absFloat32x4(Operand in, FloatRegister out); - - void bitwiseAndFloat32x4(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vandps(rhs, lhs, dest); - } - void bitwiseAndSimdInt(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vpand(rhs, lhs, dest); - } - - void bitwiseOrFloat32x4(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vorps(rhs, lhs, dest); - } - void bitwiseOrSimdInt(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vpor(rhs, lhs, dest); - } - - void bitwiseXorFloat32x4(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vxorps(rhs, lhs, dest); - } - void bitwiseXorSimdInt(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vpxor(rhs, lhs, dest); - } - - void bitwiseAndNotFloat32x4(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vandnps(rhs, lhs, dest); - } - void bitwiseAndNotSimdInt(FloatRegister lhs, const Operand& rhs, FloatRegister dest) { - vpandn(rhs, lhs, dest); - } - void zeroSimd128Float(FloatRegister dest) { vxorps(dest, dest, dest); } @@ -591,21 +431,42 @@ class MacroAssemblerX86Shared : public Assembler vpxor(dest, dest, dest); } - void selectSimd128(FloatRegister mask, FloatRegister onTrue, FloatRegister onFalse, - FloatRegister temp, FloatRegister output); - void selectX4(FloatRegister mask, FloatRegister onTrue, FloatRegister onFalse, - FloatRegister temp, FloatRegister output) { - if (AssemblerX86Shared::HasAVX()) - vblendvps(mask, onTrue, onFalse, output); - else - selectSimd128(mask, onTrue, onFalse, temp, output); - } - template inline void loadScalar(const Operand& src, Reg dest); template inline void storeScalar(Reg src, const Address& dest); template inline void loadAlignedVector(const Address& src, FloatRegister dest); template inline void storeAlignedVector(FloatRegister src, const Address& dest); + void loadInt32x1(const Address& src, FloatRegister dest) { + vmovd(Operand(src), dest); + } + void loadInt32x1(const BaseIndex& src, FloatRegister dest) { + vmovd(Operand(src), dest); + } + void loadInt32x2(const Address& src, FloatRegister dest) { + vmovq(Operand(src), dest); + } + void loadInt32x2(const BaseIndex& src, FloatRegister dest) { + vmovq(Operand(src), dest); + } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { + BaseIndex srcZ(src); + srcZ.offset += 2 * sizeof(int32_t); + + ScratchSimd128Scope scratch(asMasm()); + vmovq(Operand(src), dest); + vmovd(Operand(srcZ), scratch); + vmovlhps(scratch, dest, dest); + } + void loadInt32x3(const Address& src, FloatRegister dest) { + Address srcZ(src); + srcZ.offset += 2 * sizeof(int32_t); + + ScratchSimd128Scope scratch(asMasm()); + vmovq(Operand(src), dest); + vmovd(Operand(srcZ), scratch); + vmovlhps(scratch, dest, dest); + } + void loadAlignedSimd128Int(const Address& src, FloatRegister dest) { vmovdqa(Operand(src), dest); } @@ -640,6 +501,35 @@ class MacroAssemblerX86Shared : public Assembler vmovdqu(src, dest); } + void storeInt32x1(FloatRegister src, const Address& dest) { + vmovd(src, Operand(dest)); + } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { + vmovd(src, Operand(dest)); + } + void storeInt32x2(FloatRegister src, const Address& dest) { + vmovq(src, Operand(dest)); + } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { + vmovq(src, Operand(dest)); + } + void storeInt32x3(FloatRegister src, const Address& dest) { + Address destZ(dest); + destZ.offset += 2 * sizeof(int32_t); + vmovq(src, Operand(dest)); + ScratchSimd128Scope scratch(asMasm()); + vmovhlps(src, scratch, scratch); + vmovd(scratch, Operand(destZ)); + } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { + BaseIndex destZ(dest); + destZ.offset += 2 * sizeof(int32_t); + vmovq(src, Operand(dest)); + ScratchSimd128Scope scratch(asMasm()); + vmovhlps(src, scratch, scratch); + vmovd(scratch, Operand(destZ)); + } + void storeUnalignedSimd128Int(FloatRegister src, const Address& dest) { vmovdqu(src, Operand(dest)); } @@ -687,41 +577,61 @@ class MacroAssemblerX86Shared : public Assembler vsqrtps(src, dest); } - public: - void packedLeftShiftByScalarInt16x8(FloatRegister in, Register count, Register temp, FloatRegister dest); - void packedRightShiftByScalarInt16x8(FloatRegister in, Register count, Register temp, FloatRegister dest); - void packedUnsignedRightShiftByScalarInt16x8(FloatRegister in, Register count, Register temp, FloatRegister dest); - + void packedLeftShiftByScalarInt16x8(FloatRegister src, FloatRegister dest) { + vpsllw(src, dest, dest); + } void packedLeftShiftByScalarInt16x8(Imm32 count, FloatRegister dest) { - count.value &= 15; vpsllw(count, dest, dest); } + void packedRightShiftByScalarInt16x8(FloatRegister src, FloatRegister dest) { + vpsraw(src, dest, dest); + } void packedRightShiftByScalarInt16x8(Imm32 count, FloatRegister dest) { - count.value &= 15; vpsraw(count, dest, dest); } + void packedUnsignedRightShiftByScalarInt16x8(FloatRegister src, FloatRegister dest) { + vpsrlw(src, dest, dest); + } void packedUnsignedRightShiftByScalarInt16x8(Imm32 count, FloatRegister dest) { - count.value &= 15; vpsrlw(count, dest, dest); } - void packedLeftShiftByScalarInt32x4(FloatRegister in, Register count, Register temp, FloatRegister dest); - void packedRightShiftByScalarInt32x4(FloatRegister in, Register count, Register temp, FloatRegister dest); - void packedUnsignedRightShiftByScalarInt32x4(FloatRegister in, Register count, Register temp, FloatRegister dest); - + void packedLeftShiftByScalarInt32x4(FloatRegister src, FloatRegister dest) { + vpslld(src, dest, dest); + } void packedLeftShiftByScalarInt32x4(Imm32 count, FloatRegister dest) { - count.value &= 31; vpslld(count, dest, dest); } + void packedRightShiftByScalarInt32x4(FloatRegister src, FloatRegister dest) { + vpsrad(src, dest, dest); + } void packedRightShiftByScalarInt32x4(Imm32 count, FloatRegister dest) { - count.value &= 31; vpsrad(count, dest, dest); } + void packedUnsignedRightShiftByScalarInt32x4(FloatRegister src, FloatRegister dest) { + vpsrld(src, dest, dest); + } void packedUnsignedRightShiftByScalarInt32x4(Imm32 count, FloatRegister dest) { - count.value &= 31; vpsrld(count, dest, dest); } + void loadFloat32x3(const Address& src, FloatRegister dest) { + Address srcZ(src); + srcZ.offset += 2 * sizeof(float); + vmovsd(src, dest); + ScratchSimd128Scope scratch(asMasm()); + vmovss(srcZ, scratch); + vmovlhps(scratch, dest, dest); + } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { + BaseIndex srcZ(src); + srcZ.offset += 2 * sizeof(float); + vmovsd(src, dest); + ScratchSimd128Scope scratch(asMasm()); + vmovss(srcZ, scratch); + vmovlhps(scratch, dest, dest); + } + void loadAlignedSimd128Float(const Address& src, FloatRegister dest) { vmovaps(Operand(src), dest); } diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h index 78c430810378..8730adc0f616 100644 --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -143,7 +143,7 @@ static_assert(JitStackAlignment % sizeof(Value) == 0 && JitStackValueAlignment > // this architecture or not. Rather than a method in the LIRGenerator, it is // here such that it is accessible from the entire codebase. Once full support // for SIMD is reached on all tier-1 platforms, this constant can be deleted. -static constexpr bool SupportsSimd = false; +static constexpr bool SupportsSimd = true; static constexpr uint32_t SimdMemoryAlignment = 16; static_assert(CodeAlignment % SimdMemoryAlignment == 0, diff --git a/js/src/jit/x86/CodeGenerator-x86.cpp b/js/src/jit/x86/CodeGenerator-x86.cpp index ce9f4146db82..2a9b777e7960 100644 --- a/js/src/jit/x86/CodeGenerator-x86.cpp +++ b/js/src/jit/x86/CodeGenerator-x86.cpp @@ -350,6 +350,7 @@ CodeGenerator::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins) AnyRegister out = ToAnyRegister(ins->output()); Scalar::Type accessType = mir->accessType(); + MOZ_ASSERT(!Scalar::isSimdType(accessType)); OutOfLineLoadTypedArrayOutOfBounds* ool = nullptr; if (mir->needsBoundsCheck()) { @@ -382,6 +383,7 @@ CodeGenerator::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins) const LAllocation* memoryBase = ins->memoryBase(); Scalar::Type accessType = mir->accessType(); + MOZ_ASSERT(!Scalar::isSimdType(accessType)); canonicalizeIfDeterministic(accessType, value); Operand dstAddr = ptr->isBogus() diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp index ea1f154b2f02..22499e9bf9b0 100644 --- a/js/src/jit/x86/Lowering-x86.cpp +++ b/js/src/jit/x86/Lowering-x86.cpp @@ -371,6 +371,10 @@ LIRGenerator::visitWasmStore(MWasmStore* ins) case Scalar::Int16: case Scalar::Uint16: case Scalar::Int32: case Scalar::Uint32: case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: // For now, don't allow constant values. The immediate operand affects // instruction layout which affects patching. valueAlloc = useRegisterAtStart(ins->value()); @@ -447,6 +451,10 @@ LIRGenerator::visitAsmJSStoreHeap(MAsmJSStoreHeap* ins) case Scalar::Int16: case Scalar::Uint16: case Scalar::Int32: case Scalar::Uint32: case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: // For now, don't allow constant values. The immediate operand affects // instruction layout which affects patching. lir = new (alloc()) LAsmJSStoreHeap(baseAlloc, useRegisterAtStart(ins->value()), diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp index a11ee4e3bf8f..69cd9f6c843f 100644 --- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -645,6 +645,34 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, case Scalar::Float64: vmovsd(srcAddr, out.fpu()); break; + case Scalar::Float32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movss zeroes out the high lanes. + case 1: vmovss(srcAddr, out.fpu()); break; + // See comment above, which also applies to movsd. + case 2: vmovsd(srcAddr, out.fpu()); break; + case 4: vmovups(srcAddr, out.fpu()); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movd zeroes out the high lanes. + case 1: vmovd(srcAddr, out.fpu()); break; + // See comment above, which also applies to movq. + case 2: vmovq(srcAddr, out.fpu()); break; + case 4: vmovdqu(srcAddr, out.fpu()); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial load"); + vmovdqu(srcAddr, out.fpu()); + break; + case Scalar::Int16x8: + MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial load"); + vmovdqu(srcAddr, out.fpu()); + break; case Scalar::Int64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: @@ -659,6 +687,7 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd { // Atomic i64 load must use lock_cmpxchg8b. MOZ_ASSERT_IF(access.isAtomic(), access.byteSize() <= 4); + MOZ_ASSERT(!access.isSimd()); MOZ_ASSERT(srcAddr.kind() == Operand::MEM_REG_DISP || srcAddr.kind() == Operand::MEM_SCALE); memoryBarrierBefore(access.sync()); @@ -715,6 +744,10 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd } case Scalar::Float32: case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: @@ -752,6 +785,34 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu case Scalar::Float64: vmovsd(value.fpu(), dstAddr); break; + case Scalar::Float32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movss zeroes out the high lanes. + case 1: vmovss(value.fpu(), dstAddr); break; + // See comment above, which also applies to movsd. + case 2: vmovsd(value.fpu(), dstAddr); break; + case 4: vmovups(value.fpu(), dstAddr); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int32x4: + switch (access.numSimdElems()) { + // In memory-to-register mode, movd zeroes out the high lanes. + case 1: vmovd(value.fpu(), dstAddr); break; + // See comment above, which also applies to movsd. + case 2: vmovq(value.fpu(), dstAddr); break; + case 4: vmovdqu(value.fpu(), dstAddr); break; + default: MOZ_CRASH("unexpected size for partial load"); + } + break; + case Scalar::Int8x16: + MOZ_ASSERT(access.numSimdElems() == 16, "unexpected partial store"); + vmovdqu(value.fpu(), dstAddr); + break; + case Scalar::Int16x8: + MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial store"); + vmovdqu(value.fpu(), dstAddr); + break; case Scalar::Int64: MOZ_CRASH("Should be handled in storeI64."); case Scalar::MaxTypedArrayViewType: @@ -766,6 +827,7 @@ MacroAssembler::wasmStoreI64(const wasm::MemoryAccessDesc& access, Register64 va { // Atomic i64 store must use lock_cmpxchg8b. MOZ_ASSERT(!access.isAtomic()); + MOZ_ASSERT(!access.isSimd()); MOZ_ASSERT(dstAddr.kind() == Operand::MEM_REG_DISP || dstAddr.kind() == Operand::MEM_SCALE); append(access, size()); diff --git a/js/src/js.msg b/js/src/js.msg index ea121a64bd51..93dcccbf97b9 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -321,6 +321,7 @@ MSG_DEF(JSMSG_STMT_AFTER_RETURN, 0, JSEXN_WARN, "unreachable code after re MSG_DEF(JSMSG_STRICT_CODE_WITH, 0, JSEXN_SYNTAXERR, "strict mode code may not contain 'with' statements") MSG_DEF(JSMSG_STRICT_NON_SIMPLE_PARAMS, 1, JSEXN_SYNTAXERR, "\"use strict\" not allowed in function with {0} parameter") MSG_DEF(JSMSG_TEMPLSTR_UNTERM_EXPR, 0, JSEXN_SYNTAXERR, "missing } in template string") +MSG_DEF(JSMSG_SIMD_NOT_A_VECTOR, 2, JSEXN_TYPEERR, "expecting a SIMD {0} object as argument {1}") MSG_DEF(JSMSG_TOO_MANY_CASES, 0, JSEXN_INTERNALERR, "too many switch cases") MSG_DEF(JSMSG_TOO_MANY_CATCH_VARS, 0, JSEXN_SYNTAXERR, "too many catch variables") MSG_DEF(JSMSG_TOO_MANY_CON_ARGS, 0, JSEXN_SYNTAXERR, "too many constructor arguments") @@ -535,13 +536,15 @@ MSG_DEF(JSMSG_UNTERM_CLASS, 0, JSEXN_SYNTAXERR, "unterminated charact MSG_DEF(JSMSG_DEFAULT_LOCALE_ERROR, 0, JSEXN_ERR, "internal error getting the default locale") MSG_DEF(JSMSG_NO_SUCH_SELF_HOSTED_PROP,1, JSEXN_ERR, "No such property on self-hosted object: {0}") -// Typed object +// Typed object / SIMD MSG_DEF(JSMSG_INVALID_PROTOTYPE, 0, JSEXN_TYPEERR, "prototype field is not an object") MSG_DEF(JSMSG_TYPEDOBJECT_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments") MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index") MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED, 0, JSEXN_TYPEERR, "handle unattached") MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 0, JSEXN_RANGEERR, "invalid field descriptor") MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 0, JSEXN_ERR, "Type is too large to allocate") +MSG_DEF(JSMSG_SIMD_FAILED_CONVERSION, 0, JSEXN_RANGEERR, "SIMD conversion loses precision") +MSG_DEF(JSMSG_SIMD_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert SIMD value to number") // Array MSG_DEF(JSMSG_TOO_LONG_ARRAY, 0, JSEXN_TYPEERR, "Too long array") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index b076c5466e39..5c0a77575b6b 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -42,6 +42,9 @@ #include "builtin/Stream.h" #include "builtin/String.h" #include "builtin/Symbol.h" +#ifdef ENABLE_SIMD +# include "builtin/SIMDConstants.h" +#endif #ifdef ENABLE_BINARYDATA # include "builtin/TypedObject.h" #endif diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index cf495d63a8ed..63d76726bb11 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1613,6 +1613,10 @@ enum Type { MaxTypedArrayViewType, Int64, + Float32x4, + Int8x16, + Int16x8, + Int32x4 }; static inline size_t @@ -1633,6 +1637,10 @@ byteSize(Type atype) case Int64: case Float64: return 8; + case Int8x16: + case Int16x8: + case Int32x4: + case Float32x4: return 16; default: MOZ_CRASH("invalid scalar type"); @@ -1646,6 +1654,9 @@ isSignedIntType(Type atype) { case Int16: case Int32: case Int64: + case Int8x16: + case Int16x8: + case Int32x4: return true; case Uint8: case Uint8Clamped: @@ -1653,12 +1664,64 @@ isSignedIntType(Type atype) { case Uint32: case Float32: case Float64: + case Float32x4: return false; default: MOZ_CRASH("invalid scalar type"); } } +static inline bool +isSimdType(Type atype) { + switch (atype) { + case Int8: + case Uint8: + case Uint8Clamped: + case Int16: + case Uint16: + case Int32: + case Uint32: + case Int64: + case Float32: + case Float64: + return false; + case Int8x16: + case Int16x8: + case Int32x4: + case Float32x4: + return true; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + +static inline size_t +scalarByteSize(Type atype) { + switch (atype) { + case Int8x16: + return 1; + case Int16x8: + return 2; + case Int32x4: + case Float32x4: + return 4; + case Int8: + case Uint8: + case Uint8Clamped: + case Int16: + case Uint16: + case Int32: + case Uint32: + case Int64: + case Float32: + case Float64: + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid simd type"); +} + } /* namespace Scalar */ } /* namespace js */ diff --git a/js/src/moz.build b/js/src/moz.build index 8749089e8ada..210c5389d041 100755 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -197,6 +197,7 @@ UNIFIED_SOURCES += [ 'builtin/Promise.cpp', 'builtin/Reflect.cpp', 'builtin/ReflectParse.cpp', + 'builtin/SIMD.cpp', 'builtin/Stream.cpp', 'builtin/String.cpp', 'builtin/Symbol.cpp', @@ -268,6 +269,7 @@ UNIFIED_SOURCES += [ 'jit/CodeGenerator.cpp', 'jit/CompileWrappers.cpp', 'jit/Disassembler.cpp', + 'jit/EagerSimdUnbox.cpp', 'jit/EdgeCaseAnalysis.cpp', 'jit/EffectiveAddressAnalysis.cpp', 'jit/ExecutableAllocator.cpp', @@ -499,7 +501,6 @@ elif CONFIG['JS_CODEGEN_X86'] or CONFIG['JS_CODEGEN_X64']: 'jit/x86-shared/BaselineIC-x86-shared.cpp', 'jit/x86-shared/CodeGenerator-x86-shared.cpp', 'jit/x86-shared/Lowering-x86-shared.cpp', - 'jit/x86-shared/MacroAssembler-x86-shared-SIMD.cpp', 'jit/x86-shared/MacroAssembler-x86-shared.cpp', 'jit/x86-shared/MoveEmitter-x86-shared.cpp', ] @@ -699,6 +700,7 @@ FINAL_LIBRARY = 'js' if CONFIG['NIGHTLY_BUILD']: DEFINES['ENABLE_BINARYDATA'] = True + DEFINES['ENABLE_SIMD'] = True DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True DEFINES['ENABLE_WASM_SATURATING_TRUNC_OPS'] = True DEFINES['ENABLE_WASM_THREAD_OPS'] = True diff --git a/js/src/tests/jstests.list b/js/src/tests/jstests.list index 35444e09c6fc..f2512b79b021 100644 --- a/js/src/tests/jstests.list +++ b/js/src/tests/jstests.list @@ -38,7 +38,11 @@ skip-if(!this.hasOwnProperty("Intl")) script test262/built-ins/String/prototype/ # Skip intl402 tests when Intl isn't available. skip-if(!this.hasOwnProperty("Intl")) include test262/intl402/jstests.list +# Skip built-ins/Simd tests when SIMD isn't available. +skip-if(!this.hasOwnProperty("SIMD")) include test262/built-ins/Simd/jstests.list + # https://bugzilla.mozilla.org/show_bug.cgi?id=1415303 +skip-if(!this.hasOwnProperty("SharedArrayBuffer")) script non262/SIMD/load-sab-buffer-compat.js skip-if(!this.hasOwnProperty("Atomics")) include test262/built-ins/Atomics/jstests.list skip-if(!this.hasOwnProperty("SharedArrayBuffer")) include test262/built-ins/SharedArrayBuffer/jstests.list @@ -353,6 +357,19 @@ skip script test262/annexB/language/eval-code/indirect/global-if-decl-else-decl- skip script test262/annexB/language/eval-code/indirect/global-block-decl-eval-global-existing-global-init.js skip script test262/annexB/language/eval-code/indirect/global-switch-case-eval-global-existing-global-init.js +# SIMD. +skip script test262/built-ins/Simd/check.js +skip script test262/built-ins/Simd/from.js +skip script test262/built-ins/Simd/operators.js +skip script test262/built-ins/Simd/replace_lane.js +skip script test262/built-ins/Simd/shuffle.js +skip script test262/built-ins/Simd/swizzle.js + +# https://bugzilla.mozilla.org/show_bug.cgi?id=1336991 +skip script test262/built-ins/Simd/float_operators.js +skip script test262/built-ins/Simd/all_true.js +skip script test262/built-ins/Simd/any_true.js + # https://bugzilla.mozilla.org/show_bug.cgi?id=1340307 skip script test262/language/module-code/instn-resolve-empty-export.js skip script test262/language/module-code/instn-resolve-empty-import.js diff --git a/js/src/tests/non262/SIMD/ToSource.js b/js/src/tests/non262/SIMD/ToSource.js new file mode 100644 index 000000000000..e5efa12a365d --- /dev/null +++ b/js/src/tests/non262/SIMD/ToSource.js @@ -0,0 +1,56 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +function test() { + var Float32x4 = SIMD.Float32x4; + var f = Float32x4(11, 22, 33, 44); + assertEq(f.toSource(), "SIMD.Float32x4(11, 22, 33, 44)"); + + var Float64x2 = SIMD.Float64x2; + var f = Float64x2(11, 22, 33, 44); + assertEq(f.toSource(), "SIMD.Float64x2(11, 22)"); + + var Int8x16 = SIMD.Int8x16; + var f = Int8x16(11, 22, 33, 44, -11, -22, -33, -44, 1, 2, 3, 4, -1, -2, -3, -4); + assertEq(f.toSource(), "SIMD.Int8x16(11, 22, 33, 44, -11, -22, -33, -44, 1, 2, 3, 4, -1, -2, -3, -4)"); + + var Int16x8 = SIMD.Int16x8; + var f = Int16x8(11, 22, 33, 44, -11, -22, -33, -44); + assertEq(f.toSource(), "SIMD.Int16x8(11, 22, 33, 44, -11, -22, -33, -44)"); + + var Int32x4 = SIMD.Int32x4; + var f = Int32x4(11, 22, 33, 44); + assertEq(f.toSource(), "SIMD.Int32x4(11, 22, 33, 44)"); + + var Uint8x16 = SIMD.Uint8x16; + var f = Uint8x16(11, 22, 33, 44, 245, 234, 223, 212, 1, 2, 3, 4, 255, 254, 0, 250); + assertEq(f.toSource(), "SIMD.Uint8x16(11, 22, 33, 44, 245, 234, 223, 212, 1, 2, 3, 4, 255, 254, 0, 250)"); + + var Uint16x8 = SIMD.Uint16x8; + var f = Uint16x8(11, 22, 33, 44, 65535, 65534, 65533, 65532); + assertEq(f.toSource(), "SIMD.Uint16x8(11, 22, 33, 44, 65535, 65534, 65533, 65532)"); + + var Uint32x4 = SIMD.Uint32x4; + var f = Uint32x4(11, 22, 4294967295, 4294967294); + assertEq(f.toSource(), "SIMD.Uint32x4(11, 22, 4294967295, 4294967294)"); + + var Bool8x16 = SIMD.Bool8x16; + var f = Bool8x16(true, true, false, false, false, true, true, false, true, true, true, true, false, false, false, false); + assertEq(f.toSource(), "SIMD.Bool8x16(true, true, false, false, false, true, true, false, true, true, true, true, false, false, false, false)"); + + var Bool16x8 = SIMD.Bool16x8; + var f = Bool16x8(true, true, false, false, true, false, false, true); + assertEq(f.toSource(), "SIMD.Bool16x8(true, true, false, false, true, false, false, true)"); + + var Bool32x4 = SIMD.Bool32x4; + var f = Bool32x4(true, true, false, false); + assertEq(f.toSource(), "SIMD.Bool32x4(true, true, false, false)"); + + var Bool64x2 = SIMD.Bool64x2; + var f = Bool64x2(true, false); + assertEq(f.toSource(), "SIMD.Bool64x2(true, false)"); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/binary-operations.js b/js/src/tests/non262/SIMD/binary-operations.js new file mode 100644 index 000000000000..04b035309996 --- /dev/null +++ b/js/src/tests/non262/SIMD/binary-operations.js @@ -0,0 +1,785 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float32x4 = SIMD.Float32x4; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +// Float32x4. +function testFloat32x4add() { + function addf(a, b) { + return Math.fround(Math.fround(a) + Math.fround(b)); + } + + var vals = [ + [[1, 2, 3, 4], [10, 20, 30, 40]], + [[1.57, 2.27, 3.57, 4.19], [10.31, 20.49, 30.41, 40.72]], + [[NaN, -0, Infinity, -Infinity], [0, -0, -Infinity, -Infinity]] + ]; + + for (var [v,w] of vals) { + testBinaryFunc(Float32x4(...v), Float32x4(...w), Float32x4.add, addf); + } +} + +function testFloat32x4div() { + function divf(a, b) { + return Math.fround(Math.fround(a) / Math.fround(b)); + } + + var vals = [ + [[1, 2, 3, 4], [10, 20, 30, 40]], + [[1.26, 2.03, 3.17, 4.59], [11.025, 17.3768, 29.1957, 46.4049]], + [[0, -0, Infinity, -Infinity], [1, 1, -Infinity, Infinity]] + ]; + + for (var [v,w] of vals) { + testBinaryFunc(Float32x4(...v), Float32x4(...w), Float32x4.div, divf); + } +} + +function testFloat32x4mul() { + function mulf(a, b) { + return Math.fround(Math.fround(a) * Math.fround(b)); + } + + var vals = [ + [[1, 2, 3, 4], [10, 20, 30, 40]], + [[1.66, 2.57, 3.73, 4.12], [10.67, 20.68, 30.02, 40.58]], + [[NaN, -0, Infinity, -Infinity], [NaN, -0, -Infinity, 0]] + ]; + + for (var [v,w] of vals) { + testBinaryFunc(Float32x4(...v), Float32x4(...w), Float32x4.mul, mulf); + } +} + +function testFloat32x4sub() { + function subf(a, b) { + return Math.fround(Math.fround(a) - Math.fround(b)); + } + + var vals = [ + [[1, 2, 3, 4], [10, 20, 30, 40]], + [[1.34, 2.95, 3.17, 4.29], [10.18, 20.43, 30.63, 40.38]], + [[NaN, -0, -Infinity, -Infinity], [NaN, -0, Infinity, -Infinity]] + ]; + + for (var [v,w] of vals) { + testBinaryFunc(Float32x4(...v), Float32x4(...w), Float32x4.sub, subf); + } +} + +// Helper for saturating arithmetic. +// See SIMD.js, 5.1.25 Saturate(descriptor, x) +function saturate(lower, upper, x) { + x = x | 0; + if (x > upper) + return upper; + if (x < lower) + return lower; + return x; +} + +var i8x16vals = [ + [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]], + [[INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, -2, -3, -4, -5, -6, -7, -8, -9], + [1, 1, -1, -1, INT8_MAX, INT8_MAX, INT8_MIN, INT8_MIN, 8, 9, 10, 11, 12, 13, 14, 15]] +]; + +// Int8x16. +function testInt8x16add() { + function addi(a, b) { + return (a + b) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.add, addi); + } +} + +function testInt8x16and() { + function andi(a, b) { + return (a & b) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.and, andi); + } +} + +function testInt8x16mul() { + function muli(x, y) { + return (x * y) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.mul, muli); + } +} + +function testInt8x16or() { + function ori(a, b) { + return (a | b) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.or, ori); + } +} + +function testInt8x16sub() { + function subi(a, b) { + return (a - b) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.sub, subi); + } +} + +function testInt8x16xor() { + function xori(a, b) { + return (a ^ b) << 24 >> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.xor, xori); + } +} + +function testInt8x16addSaturate() { + function satadd(a, b) { + return saturate(INT8_MIN, INT8_MAX, a + b); + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.addSaturate, satadd); + } +} + +function testInt8x16subSaturate() { + function satsub(a, b) { + return saturate(INT8_MIN, INT8_MAX, a - b); + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.subSaturate, satsub); + } +} + +// Uint8x16. +function testUint8x16add() { + function addi(a, b) { + return (a + b) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.add, addi); + } +} + +function testUint8x16and() { + function andi(a, b) { + return (a & b) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.and, andi); + } +} + +function testUint8x16mul() { + function muli(x, y) { + return (x * y) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.mul, muli); + } +} + +function testUint8x16or() { + function ori(a, b) { + return (a | b) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.or, ori); + } +} + +function testUint8x16sub() { + function subi(a, b) { + return (a - b) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.sub, subi); + } +} + +function testUint8x16xor() { + function xori(a, b) { + return (a ^ b) << 24 >>> 24; + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.xor, xori); + } +} + +function testUint8x16addSaturate() { + function satadd(a, b) { + return saturate(0, UINT8_MAX, a + b); + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.addSaturate, satadd); + } +} + +function testUint8x16subSaturate() { + function satsub(a, b) { + return saturate(0, UINT8_MAX, a - b); + } + + for (var [v,w] of i8x16vals) { + testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.subSaturate, satsub); + } +} + +var i16x8vals = [ + [[1, 2, 3, 4, 5, 6, 7, 8], + [10, 20, 30, 40, 50, 60, 70, 80]], + [[INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN], + [1, 1, -1, -1, INT16_MAX, INT16_MAX, INT16_MIN, INT16_MIN]] +]; + +// Int16x8. +function testInt16x8add() { + function addi(a, b) { + return (a + b) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.add, addi); + } +} + +function testInt16x8and() { + function andi(a, b) { + return (a & b) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.and, andi); + } +} + +function testInt16x8mul() { + function muli(x, y) { + return (x * y) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.mul, muli); + } +} + +function testInt16x8or() { + function ori(a, b) { + return (a | b) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.or, ori); + } +} + +function testInt16x8sub() { + function subi(a, b) { + return (a - b) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.sub, subi); + } +} + +function testInt16x8xor() { + function xori(a, b) { + return (a ^ b) << 16 >> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.xor, xori); + } +} + +function testInt16x8addSaturate() { + function satadd(a, b) { + return saturate(INT16_MIN, INT16_MAX, a + b); + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.addSaturate, satadd); + } +} + +function testInt16x8subSaturate() { + function satsub(a, b) { + return saturate(INT16_MIN, INT16_MAX, a - b); + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.subSaturate, satsub); + } +} + +// Uint16x8. +function testUint16x8add() { + function addi(a, b) { + return (a + b) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.add, addi); + } +} + +function testUint16x8and() { + function andi(a, b) { + return (a & b) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.and, andi); + } +} + +function testUint16x8mul() { + function muli(x, y) { + return (x * y) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.mul, muli); + } +} + +function testUint16x8or() { + function ori(a, b) { + return (a | b) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.or, ori); + } +} + +function testUint16x8sub() { + function subi(a, b) { + return (a - b) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.sub, subi); + } +} + +function testUint16x8xor() { + function xori(a, b) { + return (a ^ b) << 16 >>> 16; + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.xor, xori); + } +} + +function testUint16x8addSaturate() { + function satadd(a, b) { + return saturate(0, UINT16_MAX, a + b); + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.addSaturate, satadd); + } +} + +function testUint16x8subSaturate() { + function satsub(a, b) { + return saturate(0, UINT16_MAX, a - b); + } + + for (var [v,w] of i16x8vals) { + testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.subSaturate, satsub); + } +} + +var i32x4vals = [ + [[1, 2, 3, 4], [10, 20, 30, 40]], + [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [1, -1, 0, 0]], + [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [INT32_MIN, INT32_MAX, INT32_MAX, INT32_MIN]], + [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [-1, -1, INT32_MIN, INT32_MIN]], + [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [-1, 1, INT32_MAX, INT32_MIN]], + [[UINT32_MAX, 0, UINT32_MAX, 0], [1, -1, 0, 0]], + [[UINT32_MAX, 0, UINT32_MAX, 0], [-1, -1, INT32_MIN, INT32_MIN]], + [[UINT32_MAX, 0, UINT32_MAX, 0], [1, -1, 0, 0]], + [[UINT32_MAX, 0, UINT32_MAX, 0], [-1, 1, INT32_MAX, INT32_MIN]] +]; + +// Int32x4. +function testInt32x4add() { + function addi(a, b) { + return (a + b) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.add, addi); + } +} + +function testInt32x4and() { + function andi(a, b) { + return (a & b) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.and, andi); + } +} + +function testInt32x4mul() { + function muli(x, y) { + // Deal with lost precision in the 53-bit double mantissa. + // Compute two 48-bit products. Truncate and combine them. + var hi = (x * (y >>> 16)) | 0; + var lo = (x * (y & 0xffff)) | 0; + return (lo + (hi << 16)) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.mul, muli); + } +} + +function testInt32x4or() { + function ori(a, b) { + return (a | b) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.or, ori); + } +} + +function testInt32x4sub() { + function subi(a, b) { + return (a - b) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.sub, subi); + } +} + +function testInt32x4xor() { + function xori(a, b) { + return (a ^ b) | 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Int32x4(...v), Int32x4(...w), Int32x4.xor, xori); + } +} + +// Uint32x4. +function testUint32x4add() { + function addi(a, b) { + return (a + b) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.add, addi); + } +} + +function testUint32x4and() { + function andi(a, b) { + return (a & b) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.and, andi); + } +} + +function testUint32x4mul() { + function muli(x, y) { + // Deal with lost precision in the 53-bit double mantissa. + // Compute two 48-bit products. Truncate and combine them. + var hi = (x * (y >>> 16)) >>> 0; + var lo = (x * (y & 0xffff)) >>> 0; + return (lo + (hi << 16)) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.mul, muli); + } +} + +function testUint32x4or() { + function ori(a, b) { + return (a | b) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.or, ori); + } +} + +function testUint32x4sub() { + function subi(a, b) { + return (a - b) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.sub, subi); + } +} + +function testUint32x4xor() { + function xori(a, b) { + return (a ^ b) >>> 0; + } + + for (var [v,w] of i32x4vals) { + testBinaryFunc(Uint32x4(...v), Uint32x4(...w), Uint32x4.xor, xori); + } +} + +var b8x16vals = [ + [[true, true, true, true, false, false, false, false, true, true, true, true, false, false, false, false], + [false, true, false, true, false, true, false, true, true, true, true, true, false, false, false, false]] +]; + +function testBool8x16and() { + function andb(a, b) { + return a && b; + } + + for (var [v,w] of b8x16vals) { + testBinaryFunc(Bool8x16(...v), Bool8x16(...w), Bool8x16.and, andb); + } +} + +function testBool8x16or() { + function orb(a, b) { + return a || b; + } + + for (var [v,w] of b8x16vals) { + testBinaryFunc(Bool8x16(...v), Bool8x16(...w), Bool8x16.or, orb); + } +} + +function testBool8x16xor() { + function xorb(a, b) { + return a != b; + } + + for (var [v,w] of b8x16vals) { + testBinaryFunc(Bool8x16(...v), Bool8x16(...w), Bool8x16.xor, xorb); + } +} + +var b16x8vals = [ + [[true, true, true, true, false, false, false, false], + [false, true, false, true, false, true, false, true]] +]; + +function testBool16x8and() { + function andb(a, b) { + return a && b; + } + + for (var [v,w] of b16x8vals) { + testBinaryFunc(Bool16x8(...v), Bool16x8(...w), Bool16x8.and, andb); + } +} + +function testBool16x8or() { + function orb(a, b) { + return a || b; + } + + for (var [v,w] of b16x8vals) { + testBinaryFunc(Bool16x8(...v), Bool16x8(...w), Bool16x8.or, orb); + } +} + +function testBool16x8xor() { + function xorb(a, b) { + return a != b; + } + + for (var [v,w] of b16x8vals) { + testBinaryFunc(Bool16x8(...v), Bool16x8(...w), Bool16x8.xor, xorb); + } +} + +var b32x4vals = [ + [[true, true, false, false], [false, true, false, true]] +]; + +function testBool32x4and() { + function andb(a, b) { + return a && b; + } + + for (var [v,w] of b32x4vals) { + testBinaryFunc(Bool32x4(...v), Bool32x4(...w), Bool32x4.and, andb); + } +} + +function testBool32x4or() { + function orb(a, b) { + return a || b; + } + + for (var [v,w] of b32x4vals) { + testBinaryFunc(Bool32x4(...v), Bool32x4(...w), Bool32x4.or, orb); + } +} + +function testBool32x4xor() { + function xorb(a, b) { + return a != b; + } + + for (var [v,w] of b32x4vals) { + testBinaryFunc(Bool32x4(...v), Bool32x4(...w), Bool32x4.xor, xorb); + } +} + +var b64x2vals = [ + [[false, false], [false, true], [true, false], [true, true]] +]; + +function testBool64x2and() { + function andb(a, b) { + return a && b; + } + + for (var [v,w] of b64x2vals) { + testBinaryFunc(Bool64x2(...v), Bool64x2(...w), Bool64x2.and, andb); + } +} + +function testBool64x2or() { + function orb(a, b) { + return a || b; + } + + for (var [v,w] of b64x2vals) { + testBinaryFunc(Bool64x2(...v), Bool64x2(...w), Bool64x2.or, orb); + } +} + +function testBool64x2xor() { + function xorb(a, b) { + return a != b; + } + + for (var [v,w] of b64x2vals) { + testBinaryFunc(Bool64x2(...v), Bool64x2(...w), Bool64x2.xor, xorb); + } +} + +function test() { + testFloat32x4add(); + testFloat32x4div(); + testFloat32x4mul(); + testFloat32x4sub(); + + testInt8x16add(); + testInt8x16and(); + testInt8x16mul(); + testInt8x16or(); + testInt8x16sub(); + testInt8x16xor(); + testInt8x16addSaturate(); + testInt8x16subSaturate(); + + testUint8x16add(); + testUint8x16and(); + testUint8x16mul(); + testUint8x16or(); + testUint8x16sub(); + testUint8x16xor(); + testUint8x16addSaturate(); + testUint8x16subSaturate(); + + testInt16x8add(); + testInt16x8and(); + testInt16x8mul(); + testInt16x8or(); + testInt16x8sub(); + testInt16x8xor(); + testInt16x8addSaturate(); + testInt16x8subSaturate(); + + testUint16x8add(); + testUint16x8and(); + testUint16x8mul(); + testUint16x8or(); + testUint16x8sub(); + testUint16x8xor(); + testUint16x8addSaturate(); + testUint16x8subSaturate(); + + testInt32x4add(); + testInt32x4and(); + testInt32x4mul(); + testInt32x4or(); + testInt32x4sub(); + testInt32x4xor(); + + testUint32x4add(); + testUint32x4and(); + testUint32x4mul(); + testUint32x4or(); + testUint32x4sub(); + testUint32x4xor(); + + testBool8x16and(); + testBool8x16or(); + testBool8x16xor(); + + testBool16x8and(); + testBool16x8or(); + testBool16x8xor(); + + testBool32x4and(); + testBool32x4or(); + testBool32x4xor(); + + testBool64x2and(); + testBool64x2or(); + testBool64x2xor(); + + if (typeof reportCompare === "function") { + reportCompare(true, true); + } +} + +test(); diff --git a/js/src/tests/non262/SIMD/browser.js b/js/src/tests/non262/SIMD/browser.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/js/src/tests/non262/SIMD/bug1023145.js b/js/src/tests/non262/SIMD/bug1023145.js new file mode 100644 index 000000000000..45f44afb5a61 --- /dev/null +++ b/js/src/tests/non262/SIMD/bug1023145.js @@ -0,0 +1,14 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +delete Object.prototype.__proto__; +var Int32x4 = SIMD.Int32x4; +var ar = Int32x4.array(1); +var array = new ar([Int32x4(1, 2, 3, 4)]); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/bug953270.js b/js/src/tests/non262/SIMD/bug953270.js new file mode 100644 index 000000000000..db6a0d7cba26 --- /dev/null +++ b/js/src/tests/non262/SIMD/bug953270.js @@ -0,0 +1,24 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +// Check that NaN normalization is applied when extracting the x lane +// out, after bit conversion has occurred. + +var Int32x4 = SIMD.Int32x4; +var a = Int32x4((4294967295), 200, 300, 400); +var c = SIMD.Float32x4.fromInt32x4Bits(a); + +// NaN canonicalization occurs when extracting out x lane: +assertEq(SIMD.Float32x4.extractLane(c, 0), NaN); + +// but underlying bits are faithfully transmitted +// (though reinterpreted as a signed integer): +var d = SIMD.Int32x4.fromFloat32x4Bits(c); +assertEq(SIMD.Int32x4.extractLane(d, 0), -1); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/check.js b/js/src/tests/non262/SIMD/check.js new file mode 100644 index 000000000000..77ef381be5f1 --- /dev/null +++ b/js/src/tests/non262/SIMD/check.js @@ -0,0 +1,214 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +function test() { + + var i4 = SIMD.Int32x4(1,2,3,4); + var i8 = SIMD.Int16x8(1,2,3,4,5,6,7,8); + var i16 = SIMD.Int8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); + var u4 = SIMD.Uint32x4(1,2,3,4); + var u8 = SIMD.Uint16x8(1,2,3,4,5,6,7,8); + var u16 = SIMD.Uint8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); + var f4 = SIMD.Float32x4(NaN, -0, Infinity, 13.37); + var f2 = SIMD.Float64x2(-0, 13.37); + var b2 = SIMD.Bool64x2(true, false); + var b4 = SIMD.Bool32x4(true, true, false, false); + var b8 = SIMD.Bool16x8(true, true, false, false, true, true, false, false); + var b16 = SIMD.Bool8x16(true, true, false, false, true, true, false, false, true, true, false, false, true, true, false, false); + + var ci4 = SIMD.Int32x4.check(i4); + assertEqX4(ci4, simdToArray(i4)); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.check({}), TypeError); + + var ci8 = SIMD.Int16x8.check(i8); + assertEqX8(ci8, simdToArray(i8)); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.check({}), TypeError); + + var ci16 = SIMD.Int8x16.check(i16); + assertEqX16(ci16, simdToArray(i16)); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.check({}), TypeError); + + var cu4 = SIMD.Uint32x4.check(u4); + assertEqX4(cu4, simdToArray(u4)); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.check({}), TypeError); + + var cu8 = SIMD.Uint16x8.check(u8); + assertEqX8(cu8, simdToArray(u8)); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.check({}), TypeError); + + var cu16 = SIMD.Uint8x16.check(u16); + assertEqX16(cu16, simdToArray(u16)); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.check({}), TypeError); + + var cf4 = SIMD.Float32x4.check(f4); + assertEqX4(cf4, simdToArray(f4)); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.check({}), TypeError); + + var cf2 = SIMD.Float64x2.check(f2); + assertEqX2(cf2, simdToArray(f2)); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.check({}), TypeError); + + var cb2 = SIMD.Bool64x2.check(b2); + assertEqX2(cb2, simdToArray(b2)); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool64x2.check({}), TypeError); + + var cb4 = SIMD.Bool32x4.check(b4); + assertEqX4(cb4, simdToArray(b4)); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool32x4.check({}), TypeError); + + var cb8 = SIMD.Bool16x8.check(b8); + assertEqX8(cb8, simdToArray(b8)); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check(b16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool16x8.check({}), TypeError); + + var cb16 = SIMD.Bool8x16.check(b16); + assertEqX16(cb16, simdToArray(b16)); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(f4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(f2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(i4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(i8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(i16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(u4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(u8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(u16), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(b2), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(b4), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check(b8), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check("i swear i'm a vector"), TypeError); + assertThrowsInstanceOf(() => SIMD.Bool8x16.check({}), TypeError); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); + diff --git a/js/src/tests/non262/SIMD/comparisons.js b/js/src/tests/non262/SIMD/comparisons.js new file mode 100644 index 000000000000..2ca54b14e195 --- /dev/null +++ b/js/src/tests/non262/SIMD/comparisons.js @@ -0,0 +1,349 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +var fround = Math.fround; + + +function testEqualFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.equal, (x, y) => fround(x) == fround(y), Bool32x4); +} +function testNotEqualFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.notEqual, (x, y) => fround(x) != fround(y), Bool32x4); +} +function testLessThanFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.lessThan, (x, y) => fround(x) < fround(y), Bool32x4); +} +function testLessThanOrEqualFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.lessThanOrEqual, (x, y) => fround(x) <= fround(y), Bool32x4); +} +function testGreaterThanFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.greaterThan, (x, y) => fround(x) > fround(y), Bool32x4); +} +function testGreaterThanOrEqualFloat32x4(v, w) { + testBinaryCompare(v, w, Float32x4.greaterThanOrEqual, (x, y) => fround(x) >= fround(y), Bool32x4); +} + +function testEqualFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.equal, (x, y) => x == y, Bool64x2); +} +function testNotEqualFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.notEqual, (x, y) => x != y, Bool64x2); +} +function testLessThanFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.lessThan, (x, y) => x < y, Bool64x2); +} +function testLessThanOrEqualFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.lessThanOrEqual, (x, y) => x <= y, Bool64x2); +} +function testGreaterThanFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.greaterThan, (x, y) => x > y, Bool64x2); +} +function testGreaterThanOrEqualFloat64x2(v, w) { + testBinaryCompare(v, w, Float64x2.greaterThanOrEqual, (x, y) => x >= y, Bool64x2); +} + +function testEqualInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.equal, (x, y) => x == y, Bool8x16); +} +function testNotEqualInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.notEqual, (x, y) => x != y, Bool8x16); +} +function testLessThanInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.lessThan, (x, y) => x < y, Bool8x16); +} +function testLessThanOrEqualInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.lessThanOrEqual, (x, y) => x <= y, Bool8x16); +} +function testGreaterThanInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.greaterThan, (x, y) => x > y, Bool8x16); +} +function testGreaterThanOrEqualInt8x16(v, w) { + testBinaryCompare(v, w, Int8x16.greaterThanOrEqual, (x, y) => x >= y, Bool8x16); +} + +function testEqualInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.equal, (x, y) => x == y, Bool16x8); +} +function testNotEqualInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.notEqual, (x, y) => x != y, Bool16x8); +} +function testLessThanInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.lessThan, (x, y) => x < y, Bool16x8); +} +function testLessThanOrEqualInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.lessThanOrEqual, (x, y) => x <= y, Bool16x8); +} +function testGreaterThanInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.greaterThan, (x, y) => x > y, Bool16x8); +} +function testGreaterThanOrEqualInt16x8(v, w) { + testBinaryCompare(v, w, Int16x8.greaterThanOrEqual, (x, y) => x >= y, Bool16x8); +} + +function testEqualInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.equal, (x, y) => x == y, Bool32x4); +} +function testNotEqualInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.notEqual, (x, y) => x != y, Bool32x4); +} +function testLessThanInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.lessThan, (x, y) => x < y, Bool32x4); +} +function testLessThanOrEqualInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.lessThanOrEqual, (x, y) => x <= y, Bool32x4); +} +function testGreaterThanInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.greaterThan, (x, y) => x > y, Bool32x4); +} +function testGreaterThanOrEqualInt32x4(v, w) { + testBinaryCompare(v, w, Int32x4.greaterThanOrEqual, (x, y) => x >= y, Bool32x4); +} + +function testEqualUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.equal, (x, y) => x == y, Bool8x16); +} +function testNotEqualUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.notEqual, (x, y) => x != y, Bool8x16); +} +function testLessThanUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.lessThan, (x, y) => x < y, Bool8x16); +} +function testLessThanOrEqualUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.lessThanOrEqual, (x, y) => x <= y, Bool8x16); +} +function testGreaterThanUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.greaterThan, (x, y) => x > y, Bool8x16); +} +function testGreaterThanOrEqualUint8x16(v, w) { + testBinaryCompare(v, w, Uint8x16.greaterThanOrEqual, (x, y) => x >= y, Bool8x16); +} + +function testEqualUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.equal, (x, y) => x == y, Bool16x8); +} +function testNotEqualUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.notEqual, (x, y) => x != y, Bool16x8); +} +function testLessThanUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.lessThan, (x, y) => x < y, Bool16x8); +} +function testLessThanOrEqualUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.lessThanOrEqual, (x, y) => x <= y, Bool16x8); +} +function testGreaterThanUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.greaterThan, (x, y) => x > y, Bool16x8); +} +function testGreaterThanOrEqualUint16x8(v, w) { + testBinaryCompare(v, w, Uint16x8.greaterThanOrEqual, (x, y) => x >= y, Bool16x8); +} + +function testEqualUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.equal, (x, y) => x == y, Bool32x4); +} +function testNotEqualUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.notEqual, (x, y) => x != y, Bool32x4); +} +function testLessThanUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.lessThan, (x, y) => x < y, Bool32x4); +} +function testLessThanOrEqualUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.lessThanOrEqual, (x, y) => x <= y, Bool32x4); +} +function testGreaterThanUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.greaterThan, (x, y) => x > y, Bool32x4); +} +function testGreaterThanOrEqualUint32x4(v, w) { + testBinaryCompare(v, w, Uint32x4.greaterThanOrEqual, (x, y) => x >= y, Bool32x4); +} + +function test() { + var Float32x4val = [ + Float32x4(1, 20, 30, 4), + Float32x4(10, 2, 3, 40), + Float32x4(9.999, 2.1234, 30.4443, 4), + Float32x4(10, 2.1233, 30.4444, 4.0001), + Float32x4(NaN, -Infinity, +Infinity, -0), + Float32x4(+Infinity, NaN, -0, -Infinity), + Float32x4(13.37, 42.42, NaN, 0) + ]; + + var v, w; + for (v of Float32x4val) { + for (w of Float32x4val) { + testEqualFloat32x4(v, w); + testNotEqualFloat32x4(v, w); + testLessThanFloat32x4(v, w); + testLessThanOrEqualFloat32x4(v, w); + testGreaterThanFloat32x4(v, w); + testGreaterThanOrEqualFloat32x4(v, w); + } + } + + var Float64x2val = [ + Float64x2(1, 20), + Float64x2(10, 2), + Float64x2(9.999, 2.1234), + Float64x2(10, 2.1233), + Float64x2(30.4443, 4), + Float64x2(30.4444, 4.0001), + Float64x2(NaN, -Infinity), + Float64x2(+Infinity, NaN), + Float64x2(+Infinity, -0), + Float64x2(-0, -Infinity), + Float64x2(13.37, 42.42), + Float64x2(NaN, 0) + ]; + + for (v of Float64x2val) { + for (w of Float64x2val) { + testEqualFloat64x2(v, w); + testNotEqualFloat64x2(v, w); + testLessThanFloat64x2(v, w); + testLessThanOrEqualFloat64x2(v, w); + testGreaterThanFloat64x2(v, w); + testGreaterThanOrEqualFloat64x2(v, w); + } + } + + var Int8x16val = [ + Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Int8x16(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16), + Int8x16(-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16), + Int8x16(1, -2, 3, -4, 5, -6, 7, -8, 9, -10, 11, -12, 13, -14, 15, -16), + Int8x16(INT8_MAX, INT8_MAX, INT8_MIN, INT8_MIN, INT8_MIN + 1, INT8_MAX - 1, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16), + Int8x16(INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX - 1, INT8_MIN + 1, 7, 8, 9, 10, 11, 12, 13, 14, 15, -16) + ]; + + for (v of Int8x16val) { + for (w of Int8x16val) { + testEqualInt8x16(v, w); + testNotEqualInt8x16(v, w); + testLessThanInt8x16(v, w); + testLessThanOrEqualInt8x16(v, w); + testGreaterThanInt8x16(v, w); + testGreaterThanOrEqualInt8x16(v, w); + } + } + + var Int16x8val = [ + Int16x8(1, 2, 3, 4, 5, 6, 7, 8), + Int16x8(-1, -2, -3, -4, -5, -6, -7, -8), + Int16x8(-1, 2, -3, 4, -5, 6, -7, 8), + Int16x8(1, -2, 3, -4, 5, -6, 7, -8), + Int16x8(INT16_MAX, INT16_MAX, INT16_MIN, INT16_MIN, INT16_MIN + 1, INT16_MAX - 1, -7, -8), + Int16x8(INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX - 1, INT16_MIN + 1, 7, -8) + ]; + + for (v of Int16x8val) { + for (w of Int16x8val) { + testEqualInt16x8(v, w); + testNotEqualInt16x8(v, w); + testLessThanInt16x8(v, w); + testLessThanOrEqualInt16x8(v, w); + testGreaterThanInt16x8(v, w); + testGreaterThanOrEqualInt16x8(v, w); + } + } + + var Int32x4val = [ + Int32x4(1, 2, 3, 4), + Int32x4(-1, -2, -3, -4), + Int32x4(-1, 2, -3, 4), + Int32x4(1, -2, 3, -4), + Int32x4(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), + Int32x4(INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN) + ]; + + for (v of Int32x4val) { + for (w of Int32x4val) { + testEqualInt32x4(v, w); + testNotEqualInt32x4(v, w); + testLessThanInt32x4(v, w); + testLessThanOrEqualInt32x4(v, w); + testGreaterThanInt32x4(v, w); + testGreaterThanOrEqualInt32x4(v, w); + } + } + + var Uint8x16val = [ + Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Uint8x16(-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16), + Uint8x16(-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16), + Uint8x16(1, -2, 3, -4, 5, -6, 7, -8, 9, -10, 11, -12, 13, -14, 15, -16), + Uint8x16(UINT8_MAX, UINT8_MAX, 0, 0, 0 + 1, UINT8_MAX - 1, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16), + Uint8x16(UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX - 1, 0 + 1, 7, 8, 9, 10, 11, 12, 13, 14, 15, -16) + ]; + + for (v of Uint8x16val) { + for (w of Uint8x16val) { + testEqualUint8x16(v, w); + testNotEqualUint8x16(v, w); + testLessThanUint8x16(v, w); + testLessThanOrEqualUint8x16(v, w); + testGreaterThanUint8x16(v, w); + testGreaterThanOrEqualUint8x16(v, w); + } + } + + var Uint16x8val = [ + Uint16x8(1, 2, 3, 4, 5, 6, 7, 8), + Uint16x8(-1, -2, -3, -4, -5, -6, -7, -8), + Uint16x8(-1, 2, -3, 4, -5, 6, -7, 8), + Uint16x8(1, -2, 3, -4, 5, -6, 7, -8), + Uint16x8(UINT16_MAX, UINT16_MAX, 0, 0, 0 + 1, UINT16_MAX - 1, -7, -8), + Uint16x8(UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX - 1, 0 + 1, 7, -8) + ]; + + for (v of Uint16x8val) { + for (w of Uint16x8val) { + testEqualUint16x8(v, w); + testNotEqualUint16x8(v, w); + testLessThanUint16x8(v, w); + testLessThanOrEqualUint16x8(v, w); + testGreaterThanUint16x8(v, w); + testGreaterThanOrEqualUint16x8(v, w); + } + } + + var Uint32x4val = [ + Uint32x4(1, 2, 3, 4), + Uint32x4(-1, -2, -3, -4), + Uint32x4(-1, 2, -3, 4), + Uint32x4(1, -2, 3, -4), + Uint32x4(UINT32_MAX, UINT32_MAX, 0, 0), + Uint32x4(UINT32_MAX, 0, UINT32_MAX, 0) + ]; + + for (v of Uint32x4val) { + for (w of Uint32x4val) { + testEqualUint32x4(v, w); + testNotEqualUint32x4(v, w); + testLessThanUint32x4(v, w); + testLessThanOrEqualUint32x4(v, w); + testGreaterThanUint32x4(v, w); + testGreaterThanOrEqualUint32x4(v, w); + } + } + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/constructors.js b/js/src/tests/non262/SIMD/constructors.js new file mode 100644 index 000000000000..a10edb343efa --- /dev/null +++ b/js/src/tests/non262/SIMD/constructors.js @@ -0,0 +1,226 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +var Float64x2 = SIMD.Float64x2; +var Float32x4 = SIMD.Float32x4; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +function TestInt8x16Ctor() { + // Constructors. + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), [1,2,3,4,5,6,7,8,9,10,11,12,13,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), [1,2,3,4,5,6,7,8,9,10,11,12,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), [1,2,3,4,5,6,7,8,9,10,11,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1,2,3,4,5,6,7,8,9,10,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9), [1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8), [1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7), [1,2,3,4,5,6,7,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6), [1,2,3,4,5,6,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5), [1,2,3,4,5,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4), [1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3), [1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2), [1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1), [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + assertEqX16(Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); +} + +function TestInt16x8Ctor() { + // Constructors. + assertEqX8(Int16x8(1, 2, 3, 4, 5, 6, 7, 8), [1,2,3,4,5,6,7,8]); + assertEqX8(Int16x8(1, 2, 3, 4, 5, 6, 7), [1,2,3,4,5,6,7,0]); + assertEqX8(Int16x8(1, 2, 3, 4, 5, 6), [1,2,3,4,5,6,0,0]); + assertEqX8(Int16x8(1, 2, 3, 4, 5), [1,2,3,4,5,0,0,0]); + assertEqX8(Int16x8(1, 2, 3, 4), [1,2,3,4,0,0,0,0]); + assertEqX8(Int16x8(1, 2, 3), [1,2,3,0,0,0,0,0]); + assertEqX8(Int16x8(1, 2), [1,2,0,0,0,0,0,0]); + assertEqX8(Int16x8(1), [1,0,0,0,0,0,0,0]); + assertEqX8(Int16x8(), [0,0,0,0,0,0,0,0]); + assertEqX8(Int16x8(1, 2, 3, 4, 5, 6, 7, 8, 9), [1,2,3,4,5,6,7,8]); + assertEqX8(Int16x8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1,2,3,4,5,6,7,8]); +} + +function TestInt32x4Ctor() { + // Constructors. + assertEqX4(Int32x4(1, 2, 3, 4), [1,2,3,4]); + assertEqX4(Int32x4(1, 2, 3), [1,2,3,0]); + assertEqX4(Int32x4(1, 2), [1,2,0,0]); + assertEqX4(Int32x4(1), [1,0,0,0]); + assertEqX4(Int32x4(), [0,0,0,0]); + assertEqX4(Int32x4(1, 2, 3, 4, 5), [1,2,3,4]); + assertEqX4(Int32x4(1, 2, 3, 4, 5, 6), [1,2,3,4]); +} + +function TestUint8x16Ctor() { + // Constructors. + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), [1,2,3,4,5,6,7,8,9,10,11,12,13,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), [1,2,3,4,5,6,7,8,9,10,11,12,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), [1,2,3,4,5,6,7,8,9,10,11,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1,2,3,4,5,6,7,8,9,10,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9), [1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8), [1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7), [1,2,3,4,5,6,7,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6), [1,2,3,4,5,6,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5), [1,2,3,4,5,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4), [1,2,3,4,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3), [1,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2), [1,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1), [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); + assertEqX16(Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]); +} + +function TestUint16x8Ctor() { + // Constructors. + assertEqX8(Uint16x8(1, 2, 3, 4, 5, 6, 7, 8), [1,2,3,4,5,6,7,8]); + assertEqX8(Uint16x8(1, 2, 3, 4, 5, 6, 7), [1,2,3,4,5,6,7,0]); + assertEqX8(Uint16x8(1, 2, 3, 4, 5, 6), [1,2,3,4,5,6,0,0]); + assertEqX8(Uint16x8(1, 2, 3, 4, 5), [1,2,3,4,5,0,0,0]); + assertEqX8(Uint16x8(1, 2, 3, 4), [1,2,3,4,0,0,0,0]); + assertEqX8(Uint16x8(1, 2, 3), [1,2,3,0,0,0,0,0]); + assertEqX8(Uint16x8(1, 2), [1,2,0,0,0,0,0,0]); + assertEqX8(Uint16x8(1), [1,0,0,0,0,0,0,0]); + assertEqX8(Uint16x8(), [0,0,0,0,0,0,0,0]); + assertEqX8(Uint16x8(1, 2, 3, 4, 5, 6, 7, 8, 9), [1,2,3,4,5,6,7,8]); + assertEqX8(Uint16x8(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), [1,2,3,4,5,6,7,8]); +} + +function TestUint32x4Ctor() { + // Constructors. + assertEqX4(Uint32x4(1, 2, 3, 4), [1,2,3,4]); + assertEqX4(Uint32x4(1, 2, 3), [1,2,3,0]); + assertEqX4(Uint32x4(1, 2), [1,2,0,0]); + assertEqX4(Uint32x4(1), [1,0,0,0]); + assertEqX4(Uint32x4(), [0,0,0,0]); + assertEqX4(Uint32x4(1, 2, 3, 4, 5), [1,2,3,4]); + assertEqX4(Uint32x4(1, 2, 3, 4, 5, 6), [1,2,3,4]); +} + +function TestFloat32x4Ctor() { + assertEqX4(Float32x4(1, 2, 3, 4), [1,2,3,4]); + assertEqX4(Float32x4(1, 2, 3), [1,2,3,NaN]); + assertEqX4(Float32x4(1, 2), [1,2,NaN,NaN]); + assertEqX4(Float32x4(1), [1,NaN,NaN,NaN]); + assertEqX4(Float32x4(), [NaN,NaN,NaN,NaN]); + assertEqX4(Float32x4(1, 2, 3, 4, 5), [1,2,3,4]); + assertEqX4(Float32x4(1, 2, 3, 4, 5, 6), [1,2,3,4]); +} + +function TestFloat64x2Ctor() { + assertEqX2(Float64x2(1, 2), [1,2]); + assertEqX2(Float64x2(1), [1,NaN]); + assertEqX2(Float64x2(), [NaN,NaN]); + assertEqX2(Float64x2(1, 2, 3), [1,2]); + assertEqX2(Float64x2(1, 2, 3, 4), [1,2]); + assertEqX2(Float64x2(1, 2, 3, 4, 5), [1,2]); + assertEqX2(Float64x2(1, 2, 3, 4, 5), [1,2]); + assertEqX2(Float64x2(1, 2, 3, 4, 5, 6), [1,2]); +} + +function TestBool8x16Ctor() { + assertEqX16(Bool8x16(false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true), + [false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true]); + assertEqX16(Bool8x16(false, true, true, false, false, true, true, false, false, true, true, false, false, true, true), + [false, true, true, false, false, true, true, false, false, true, true, false, false, true, true, false]); + assertEqX16(Bool8x16(true, true, false, false, true, true, false, false, true, true, false, false, true, true), + [true, true, false, false, true, true, false, false, true, true, false, false, true, true, false, false]); + assertEqX16(Bool8x16(true, false, false, true, true, false, false, true, true, false, false, true, true), + [true, false, false, true, true, false, false, true, true, false, false, true, true, false, false, false]); + assertEqX16(Bool8x16(false, false, true, true, false, false, true, true, false, false, true, true), + [false, false, true, true, false, false, true, true, false, false, true, true, false, false, false, false]); + assertEqX16(Bool8x16(false, true, true, false, false, true, true, false, false, true, true), + [false, true, true, false, false, true, true, false, false, true, true, false, false, false, false, false]); + assertEqX16(Bool8x16(true, true, false, false, true, true, false, false, true, true), + [true, true, false, false, true, true, false, false, true, true, false, false, false, false, false, false]); + assertEqX16(Bool8x16(true, false, false, true, true, false, false, true, true), + [true, false, false, true, true, false, false, true, true, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(false, false, true, true, false, false, true, true), + [false, false, true, true, false, false, true, true, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(false, true, true, false, false, true, true), + [false, true, true, false, false, true, true, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(true, true, false, false, true, true), + [true, true, false, false, true, true, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(true, false, false, true, true), + [true, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(false, false, true, true), + [false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(false, true, true), + [false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(true, true), + [true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(true), + [true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(), + [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16(false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true, false), + [false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true]); + assertEqX16(Bool8x16(false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true, false, true), + [false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true]); +} + +function TestBool16x8Ctor() { + assertEqX8(Bool16x8(false, false, true, true, false, false, true, true), [false, false, true, true, false, false, true, true]); + assertEqX8(Bool16x8(false, true, true, false, false, true, true), [false, true, true, false, false, true, true, false]); + assertEqX8(Bool16x8(true, true, false, false, true, true), [true, true, false, false, true, true, false, false]); + assertEqX8(Bool16x8(true, false, false, true, true), [true, false, false, true, true, false, false, false]); + assertEqX8(Bool16x8(false, false, true, true), [false, false, true, true, false, false, false, false]); + assertEqX8(Bool16x8(false, true, true), [false, true, true, false, false, false, false, false]); + assertEqX8(Bool16x8(true, true), [true, true, false, false, false, false, false, false]); + assertEqX8(Bool16x8(true), [true, false, false, false, false, false, false, false]); + assertEqX8(Bool16x8(), [false, false, false, false, false, false, false, false]); + assertEqX8(Bool16x8(false, false, true, true, false, false, true, true, true), [false, false, true, true, false, false, true, true]); + assertEqX8(Bool16x8(false, false, true, true, false, false, true, true, true, true), [false, false, true, true, false, false, true, true]); +} + +function TestBool32x4Ctor() { + assertEqX4(Bool32x4(false, false, true, true), [false, false, true, true]); + assertEqX4(Bool32x4(false, false, true), [false, false, true, false]); + assertEqX4(Bool32x4(false, true), [false, true, false, false]); + assertEqX4(Bool32x4(true), [true, false, false, false]); + assertEqX4(Bool32x4(), [false, false, false, false]); + assertEqX4(Bool32x4(false, false, true, true, false), [false, false, true, true]); + assertEqX4(Bool32x4(false, false, true, true, false, true), [false, false, true, true]); +} + +function TestBool64x2Ctor() { + assertEqX2(Bool64x2(false, true), [false, true]); + assertEqX2(Bool64x2(true), [true, false]); + assertEqX2(Bool64x2(), [false, false]); + assertEqX2(Bool64x2(false, true, true), [false, true]); + assertEqX2(Bool64x2(false, true, true, true), [false, true]); +} + +function test() { + TestFloat32x4Ctor(); + TestFloat64x2Ctor(); + TestInt8x16Ctor(); + TestInt16x8Ctor(); + TestInt32x4Ctor(); + TestUint8x16Ctor(); + TestUint16x8Ctor(); + TestUint32x4Ctor(); + TestBool8x16Ctor(); + TestBool16x8Ctor(); + TestBool32x4Ctor(); + TestBool64x2Ctor(); + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); + diff --git a/js/src/tests/non262/SIMD/conversions.js b/js/src/tests/non262/SIMD/conversions.js new file mode 100644 index 000000000000..05b2487ac7c7 --- /dev/null +++ b/js/src/tests/non262/SIMD/conversions.js @@ -0,0 +1,1261 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; + +function testFloat32x4FromFloat64x2Bits() { + var valsExp = [ + [[2.000000473111868, 512.0001225471497], [1.0, 2.0, 3.0, 4.0]], + [[-0, NaN], [0, -0, 0, NaN]], + [[Infinity, -Infinity], [0, NaN, 0, NaN]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Float32x4.fromFloat64x2Bits(Float64x2(...v)), w); + } +} + +function testFloat32x4FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var f32 = new Float32Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [f32[0], f32[1], f32[2], f32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, + INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN]]; + for (var v of vals) { + var i = Int8x16(...v); + assertEqX4(Float32x4.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Float32x4.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testFloat32x4FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var f32 = new Float32Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [f32[0], f32[1], f32[2], f32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, + UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0]]; + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX4(Float32x4.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Float32x4.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testFloat32x4FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var f32 = new Float32Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [f32[0], f32[1], f32[2], f32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX]]; + for (var v of vals) { + var i = Int16x8(...v); + assertEqX4(Float32x4.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Float32x4.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testFloat32x4FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var f32 = new Float32Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [f32[0], f32[1], f32[2], f32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX4(Float32x4.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Float32x4.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testFloat32x4FromInt32x4() { + function expected(v) { + return v.map(Math.fround); + } + var vals = [ + [1, 2, 3, 4], + [INT32_MIN, INT32_MAX, Math.pow(2, 30) - 1, -Math.pow(2, 30)] + ]; + + for (var v of vals) { + assertEqX4(Float32x4.fromInt32x4(Int32x4(...v)), expected(v)); + } + + // Check that rounding to nearest, even is applied. + { + var num = makeFloat(0, 150 + 2, 0); + var next = makeFloat(0, 150 + 2, 1); + assertEq(num + 4, next); + + v = Float32x4.fromInt32x4(Int32x4(num, num + 1, num + 2, num + 3)); + assertEqX4(v, [num, num, /* even */ num, next]); + } + + { + var num = makeFloat(0, 150 + 2, 1); + var next = makeFloat(0, 150 + 2, 2); + assertEq(num + 4, next); + + v = Float32x4.fromInt32x4(Int32x4(num, num + 1, num + 2, num + 3)); + assertEqX4(v, [num, num, /* even */ next, next]); + } + + { + var last = makeFloat(0, 157, 0x7fffff); + + assertEq(last, Math.fround(last), "float"); + assertEq(last < Math.pow(2, 31), true, "less than 2**31"); + assertEq(last | 0, last, "it should be an integer, as exponent >= 150"); + + var diff = (Math.pow(2, 31) - 1) - last; + v = Float32x4.fromInt32x4(Int32x4(Math.pow(2, 31) - 1, + Math.pow(2, 30) + 1, + last + (diff / 2) | 0, // nearest is last + last + (diff / 2) + 1 | 0 // nearest is Math.pow(2, 31) + )); + assertEqX4(v, [Math.pow(2, 31), + Math.pow(2, 30), + last, + Math.pow(2, 31) + ]); + } +} + +function testFloat32x4FromUint32x4() { + function expected(v) { + return v.map(Math.fround); + } + var vals = [ + [1, 2, 3, 4], + [0, UINT32_MAX, Math.pow(2, 30) - 1, Math.pow(2, 31)] + ]; + + for (var v of vals) { + assertEqX4(Float32x4.fromUint32x4(Uint32x4(...v)), expected(v)); + } + + // Check that rounding to nearest, even is applied. + { + var num = makeFloat(0, 150 + 2, 0); + var next = makeFloat(0, 150 + 2, 1); + assertEq(num + 4, next); + + v = Float32x4.fromUint32x4(Uint32x4(num, num + 1, num + 2, num + 3)); + assertEqX4(v, [num, num, /* even */ num, next]); + } + + { + var num = makeFloat(0, 150 + 2, 1); + var next = makeFloat(0, 150 + 2, 2); + assertEq(num + 4, next); + + v = Float32x4.fromUint32x4(Uint32x4(num, num + 1, num + 2, num + 3)); + assertEqX4(v, [num, num, /* even */ next, next]); + } + + { + var last = makeFloat(0, 157, 0x7fffff); + + assertEq(last, Math.fround(last), "float"); + assertEq(last < Math.pow(2, 31), true, "less than 2**31"); + assertEq(last | 0, last, "it should be an integer, as exponent >= 150"); + + var diff = (Math.pow(2, 31) - 1) - last; + v = Float32x4.fromUint32x4(Uint32x4(Math.pow(2, 31) - 1, + Math.pow(2, 30) + 1, + last + (diff / 2) | 0, // nearest is last + last + (diff / 2) + 1 | 0 // nearest is Math.pow(2, 31) + )); + assertEqX4(v, [Math.pow(2, 31), + Math.pow(2, 30), + last, + Math.pow(2, 31) + ]); + } +} + +function testFloat32x4FromInt32x4Bits() { + var valsExp = [ + [[100, 200, 300, 400], [1.401298464324817e-43, 2.802596928649634e-43, 4.203895392974451e-43, 5.605193857299268e-43]], + [[INT32_MIN, INT32_MAX, 0, 0], [-0, NaN, 0, 0]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Float32x4.fromInt32x4Bits(Int32x4(...v)), w); + } +} + +function testFloat32x4FromUint32x4Bits() { + var valsExp = [ + [[100, 200, 300, 400], [1.401298464324817e-43, 2.802596928649634e-43, 4.203895392974451e-43, 5.605193857299268e-43]], + [[INT32_MIN, INT32_MAX, 0, 0], [-0, NaN, 0, 0]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Float32x4.fromUint32x4Bits(Uint32x4(...v)), w); + } +} + +function testFloat64x2FromFloat32x4Bits() { + var valsExp = [ + [[0, 1.875, 0, 2], [1.0, 2.0]], + [[NaN, -0, Infinity, -Infinity], [-1.058925634e-314, -1.404448428688076e+306]] + ]; + + for (var [v,w] of valsExp) { + assertEqX2(Float64x2.fromFloat32x4Bits(Float32x4(...v)), w); + } +} + +function testFloat64x2FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var f64 = new Float64Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [f64[0], f64[1]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, + INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN]]; + + for (var v of vals) { + var f = Int8x16(...v); + assertEqX2(Float64x2.fromInt8x16Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX2(Float64x2.fromInt8x16Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testFloat64x2FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var f64 = new Float64Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [f64[0], f64[1]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, + UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0]]; + + for (var v of vals) { + var f = Uint8x16(...v); + assertEqX2(Float64x2.fromUint8x16Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX2(Float64x2.fromUint8x16Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testFloat64x2FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var f64 = new Float64Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [f64[0], f64[1]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX]]; + + for (var v of vals) { + var f = Int16x8(...v); + assertEqX2(Float64x2.fromInt16x8Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX2(Float64x2.fromInt16x8Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testFloat64x2FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var f64 = new Float64Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [f64[0], f64[1]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + + for (var v of vals) { + var f = Uint16x8(...v); + assertEqX2(Float64x2.fromUint16x8Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX2(Float64x2.fromUint16x8Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testFloat64x2FromInt32x4Bits() { + var valsExp = [ + [[0x00000000, 0x3ff00000, 0x0000000, 0x40000000], [1.0, 2.0]], + [[0xabcdef12, 0x3ff00000, 0x21fedcba, 0x40000000], [1.0000006400213732, 2.0000002532866263]] + ]; + + for (var [v,w] of valsExp) { + assertEqX2(Float64x2.fromInt32x4Bits(Int32x4(...v)), w); + } +} + +function testFloat64x2FromUint32x4Bits() { + var valsExp = [ + [[0x00000000, 0x3ff00000, 0x0000000, 0x40000000], [1.0, 2.0]], + [[0xabcdef12, 0x3ff00000, 0x21fedcba, 0x40000000], [1.0000006400213732, 2.0000002532866263]] + ]; + + for (var [v,w] of valsExp) { + assertEqX2(Float64x2.fromUint32x4Bits(Uint32x4(...v)), w); + } +} + +function testInt32x4FromFloat32x4() { + var d = Float32x4(1.1, 2.2, 3.3, 4.6); + assertEqX4(Int32x4.fromFloat32x4(d), [1, 2, 3, 4]); + + var d = Float32x4(NaN, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Int32x4.fromFloat32x4(d), RangeError); + + var d = Float32x4(Infinity, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Int32x4.fromFloat32x4(d), RangeError); + + var d = Float32x4(-Infinity, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Int32x4.fromFloat32x4(d), RangeError); + + // Test high boundaries: float(0, 157, 0x7fffff) < INT32_MAX < float(0, 158, 0) + var d = Float32x4(makeFloat(0, 127 + 31, 0), 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Int32x4.fromFloat32x4(d), RangeError); + + var lastFloat = makeFloat(0, 127 + 30, 0x7FFFFF); + var d = Float32x4(lastFloat, 0, 0, 0); + var e = SIMD.Int32x4.fromFloat32x4(d); + assertEqX4(e, [lastFloat, 0, 0, 0]); + + // Test low boundaries + assertEq(makeFloat(1, 127 + 31, 0), INT32_MIN); + var d = Float32x4(makeFloat(1, 127 + 31, 0), 0, 0, 0); + var e = SIMD.Int32x4.fromFloat32x4(d); + assertEqX4(e, [INT32_MIN, 0, 0, 0]); + + var d = Float32x4(makeFloat(1, 127 + 31, 1), 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Int32x4.fromFloat32x4(d), RangeError); +} + +function testUint32x4FromFloat32x4() { + var d = Float32x4(1.1, 2.2, -0.9, 4.6); + assertEqX4(Uint32x4.fromFloat32x4(d), [1, 2, 0, 4]); + + var d = Float32x4(NaN, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Uint32x4.fromFloat32x4(d), RangeError); + + var d = Float32x4(Infinity, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Uint32x4.fromFloat32x4(d), RangeError); + + var d = Float32x4(-Infinity, 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Uint32x4.fromFloat32x4(d), RangeError); + + // Test high boundaries: float(0, 158, 0x7fffff) < UINT32_MAX < float(0, 159, 0) + var d = Float32x4(makeFloat(0, 127 + 32, 0), 0, 0, 0); + assertThrowsInstanceOf(() => SIMD.Uint32x4.fromFloat32x4(d), RangeError); + + var lastFloat = makeFloat(0, 127 + 31, 0x7FFFFF); + var d = Float32x4(lastFloat, 0, 0, 0); + var e = SIMD.Uint32x4.fromFloat32x4(d); + assertEqX4(e, [lastFloat, 0, 0, 0]); +} + +function testInt32x4FromFloat32x4Bits() { + var valsExp = [ + [[1, 2, 3, 4], [0x3f800000 | 0, 0x40000000 | 0, 0x40400000 | 0, 0x40800000 | 0]], + [[NaN, -0, Infinity, -Infinity], [0x7fc00000 | 0, 0x80000000 | 0, 0x7f800000 | 0, 0xff800000 | 0]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Int32x4.fromFloat32x4Bits(Float32x4(...v)), w); + } +} + +function testUint32x4FromFloat32x4Bits() { + var valsExp = [ + [[1, 2, 3, 4], [0x3f800000, 0x40000000, 0x40400000, 0x40800000]], + [[NaN, -0, Infinity, -Infinity], [0x7fc00000, 0x80000000, 0x7f800000, 0xff800000]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Uint32x4.fromFloat32x4Bits(Float32x4(...v)), w); + } +} + +function testInt32x4FromFloat64x2Bits() { + var valsExp = [ + [[1.0, 2.0], [0x00000000, 0x3FF00000, 0x00000000, 0x40000000]], + [[+Infinity, -Infinity], [0x00000000, 0x7ff00000, 0x00000000, -0x100000]], + [[-0, NaN], [0x00000000, -0x80000000, 0x00000000, 0x7ff80000]], + [[1.0000006400213732, 2.0000002532866263], [-0x543210ee, 0x3ff00000, 0x21fedcba, 0x40000000]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Int32x4.fromFloat64x2Bits(Float64x2(...v)), w); + } +} + +function testUint32x4FromFloat64x2Bits() { + var valsExp = [ + [[1.0, 2.0], [0x00000000, 0x3FF00000, 0x00000000, 0x40000000]], + [[+Infinity, -Infinity], [0x00000000, 0x7ff00000, 0x00000000, 0xfff00000]], + [[-0, NaN], [0x00000000, 0x80000000, 0x00000000, 0x7ff80000]], + [[1.0000006400213732, 2.0000002532866263], [0xabcdef12, 0x3ff00000, 0x21fedcba, 0x40000000]] + ]; + + for (var [v,w] of valsExp) { + assertEqX4(Uint32x4.fromFloat64x2Bits(Float64x2(...v)), w); + } +} + +function testInt32x4FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var i32 = new Int32Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [i32[0], i32[1], i32[2], i32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, + INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN]]; + + for (var v of vals) { + var i = Int8x16(...v); + assertEqX4(Int32x4.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Int32x4.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt32x4FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var i32 = new Int32Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [i32[0], i32[1], i32[2], i32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, + UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0]]; + + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX4(Int32x4.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Int32x4.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt32x4FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var i32 = new Int32Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [i32[0], i32[1], i32[2], i32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX]]; + + for (var v of vals) { + var i = Int16x8(...v); + assertEqX4(Int32x4.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Int32x4.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt32x4FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var i32 = new Int32Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [i32[0], i32[1], i32[2], i32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX4(Int32x4.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Int32x4.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt32x4FromUint32x4Bits() { + function expected(v, Buffer) { + var u32 = new Uint32Array(new Buffer(16)); + var i32 = new Int32Array(u32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u32[i] = asArr[i]; + return [i32[0], i32[1], i32[2], i32[3]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, UINT32_MAX, INT32_MIN, INT32_MAX]]; + + for (var v of vals) { + var i = Uint32x4(...v); + assertEqX4(Int32x4.fromUint32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Int32x4.fromUint32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt8x16FromFloat32x4Bits() { + function expected(v, Buffer) { + var f32 = new Float32Array(new Buffer(16)); + var i8 = new Int8Array(f32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) f32[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[1, -2, 3, -4], [Infinity, -Infinity, NaN, -0]]; + + for (var v of vals) { + var f = Float32x4(...v); + assertEqX16(Int8x16.fromFloat32x4Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromFloat32x4Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testInt8x16FromFloat64x2Bits() { + function expected(v, Buffer) { + var f64 = new Float64Array(new Buffer(16)); + var i8 = new Int8Array(f64.buffer); + f64[0] = Float64x2.extractLane(v, 0); + f64[1] = Float64x2.extractLane(v, 1); + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + var vals = [[1, -2], [-3, 4], [Infinity, -Infinity], [NaN, -0]]; + + for (var v of vals) { + var f = Float64x2(...v); + assertEqX16(Int8x16.fromFloat64x2Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromFloat64x2Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testInt8x16FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var i8 = new Int8Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[0, 1, -2, 3, -4, 5, INT8_MIN, UINT8_MAX, -6, 7, -8, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX16(Int8x16.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt8x16FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var i8 = new Int8Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[0, 1, -2, 3, INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX]]; + for (var v of vals) { + var i = Int16x8(...v); + assertEqX16(Int8x16.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt8x16FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var i8 = new Int8Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[0, 1, -2, UINT16_MAX, INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX]]; + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX16(Int8x16.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt8x16FromInt32x4Bits() { + function expected(v, Buffer) { + var i32 = new Int32Array(new Buffer(16)); + var i8 = new Int8Array(i32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) i32[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX]]; + for (var v of vals) { + var i = Int32x4(...v); + assertEqX16(Int8x16.fromInt32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromInt32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt8x16FromUint32x4Bits() { + function expected(v, Buffer) { + var u32 = new Uint32Array(new Buffer(16)); + var i8 = new Int8Array(u32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) u32[i] = asArr[i]; + return [i8[0], i8[1], i8[2], i8[3], i8[4], i8[5], i8[6], i8[7], + i8[8], i8[9], i8[10], i8[11], i8[12], i8[13], i8[14], i8[15]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX]]; + for (var v of vals) { + var i = Uint32x4(...v); + assertEqX16(Int8x16.fromUint32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Int8x16.fromUint32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt16x8FromFloat32x4Bits() { + function expected(v, Buffer) { + var f32 = new Float32Array(new Buffer(16)); + var i16 = new Int16Array(f32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) f32[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[1, -2, 3, -4], [Infinity, -Infinity, NaN, -0]]; + + for (var v of vals) { + var f = Float32x4(...v); + assertEqX8(Int16x8.fromFloat32x4Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromFloat32x4Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testInt16x8FromFloat64x2Bits() { + function expected(v, Buffer) { + var f64 = new Float64Array(new Buffer(16)); + var i16 = new Int16Array(f64.buffer); + f64[0] = Float64x2.extractLane(v, 0); + f64[1] = Float64x2.extractLane(v, 1); + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[1, -2], [-3, 4], [Infinity, -Infinity], [NaN, -0]]; + + for (var v of vals) { + var f = Float64x2(...v); + assertEqX8(Int16x8.fromFloat64x2Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromFloat64x2Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testInt16x8FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var i16 = new Int16Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[0, 1, -2, 3, -4, 5, INT8_MIN, INT8_MAX, -6, 7, -8, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Int8x16(...v); + assertEqX8(Int16x8.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt16x8FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var i16 = new Int16Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[0, 1, -2, 3, -4, UINT8_MAX, INT8_MIN, INT8_MAX, -6, 7, -8, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX8(Int16x8.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt16x8FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var i16 = new Int16Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX8(Int16x8.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt16x8FromInt32x4Bits() { + function expected(v, Buffer) { + var i32 = new Int32Array(new Buffer(16)); + var i16 = new Int16Array(i32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) i32[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[1, -2, -3, 4], [INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN]]; + + for (var v of vals) { + var i = Int32x4(...v); + assertEqX8(Int16x8.fromInt32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromInt32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testInt16x8FromUint32x4Bits() { + function expected(v, Buffer) { + var u32 = new Uint32Array(new Buffer(16)); + var i16 = new Int16Array(u32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) u32[i] = asArr[i]; + return [i16[0], i16[1], i16[2], i16[3], i16[4], i16[5], i16[6], i16[7]]; + } + + var vals = [[1, -2, -3, 4], [INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN]]; + + for (var v of vals) { + var i = Uint32x4(...v); + assertEqX8(Int16x8.fromUint32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Int16x8.fromUint32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint32x4FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var u32 = new Uint32Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [u32[0], u32[1], u32[2], u32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, + INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN]]; + + for (var v of vals) { + var i = Int8x16(...v); + assertEqX4(Uint32x4.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Uint32x4.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint32x4FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var u32 = new Uint32Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [u32[0], u32[1], u32[2], u32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, + UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0, UINT8_MAX, 0]]; + + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX4(Uint32x4.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Uint32x4.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint32x4FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var u32 = new Uint32Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [u32[0], u32[1], u32[2], u32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX]]; + + for (var v of vals) { + var i = Int16x8(...v); + assertEqX4(Uint32x4.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Uint32x4.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint32x4FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var u32 = new Uint32Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [u32[0], u32[1], u32[2], u32[3]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX4(Uint32x4.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Uint32x4.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint32x4FromInt32x4Bits() { + function expected(v, Buffer) { + var i32 = new Int32Array(new Buffer(16)); + var u32 = new Uint32Array(i32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i32[i] = asArr[i]; + return [u32[0], u32[1], u32[2], u32[3]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, UINT32_MAX, INT32_MIN, INT32_MAX]]; + + for (var v of vals) { + var i = Int32x4(...v); + assertEqX4(Uint32x4.fromInt32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX4(Uint32x4.fromInt32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint8x16FromFloat32x4Bits() { + function expected(v, Buffer) { + var f32 = new Float32Array(new Buffer(16)); + var u8 = new Uint8Array(f32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) f32[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[1, -2, 3, -4], [Infinity, -Infinity, NaN, -0]]; + + for (var v of vals) { + var f = Float32x4(...v); + assertEqX16(Uint8x16.fromFloat32x4Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromFloat32x4Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testUint8x16FromFloat64x2Bits() { + function expected(v, Buffer) { + var f64 = new Float64Array(new Buffer(16)); + var u8 = new Uint8Array(f64.buffer); + f64[0] = Float64x2.extractLane(v, 0); + f64[1] = Float64x2.extractLane(v, 1); + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + var vals = [[1, -2], [-3, 4], [Infinity, -Infinity], [NaN, -0]]; + + for (var v of vals) { + var f = Float64x2(...v); + assertEqX16(Uint8x16.fromFloat64x2Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromFloat64x2Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testUint8x16FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var u8 = new Uint8Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[0, 1, -2, 3, -4, 5, INT8_MIN, UINT8_MAX, -6, 7, INT8_MAX, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Int8x16(...v); + assertEqX16(Uint8x16.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint8x16FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var u8 = new Uint8Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[0, 1, -2, 3, INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX]]; + for (var v of vals) { + var i = Int16x8(...v); + assertEqX16(Uint8x16.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint8x16FromUint16x8Bits() { + function expected(v, Buffer) { + var u16 = new Uint16Array(new Buffer(16)); + var u8 = new Uint8Array(u16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) u16[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[0, 1, -2, UINT16_MAX, INT8_MIN, INT8_MAX, INT16_MIN, INT16_MAX]]; + for (var v of vals) { + var i = Uint16x8(...v); + assertEqX16(Uint8x16.fromUint16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromUint16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint8x16FromInt32x4Bits() { + function expected(v, Buffer) { + var i32 = new Int32Array(new Buffer(16)); + var u8 = new Uint8Array(i32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) i32[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX]]; + for (var v of vals) { + var i = Int32x4(...v); + assertEqX16(Uint8x16.fromInt32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromInt32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint8x16FromUint32x4Bits() { + function expected(v, Buffer) { + var u32 = new Uint32Array(new Buffer(16)); + var u8 = new Uint8Array(u32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) u32[i] = asArr[i]; + return [u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], + u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]]; + } + + var vals = [[0, 1, -2, 3], [INT8_MIN, INT8_MAX, INT32_MIN, INT32_MAX]]; + for (var v of vals) { + var i = Uint32x4(...v); + assertEqX16(Uint8x16.fromUint32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX16(Uint8x16.fromUint32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint16x8FromFloat32x4Bits() { + function expected(v, Buffer) { + var f32 = new Float32Array(new Buffer(16)); + var u16 = new Uint16Array(f32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) f32[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[1, -2, 3, -4], [Infinity, -Infinity, NaN, -0]]; + + for (var v of vals) { + var f = Float32x4(...v); + assertEqX8(Uint16x8.fromFloat32x4Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromFloat32x4Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testUint16x8FromFloat64x2Bits() { + function expected(v, Buffer) { + var f64 = new Float64Array(new Buffer(16)); + var u16 = new Uint16Array(f64.buffer); + f64[0] = Float64x2.extractLane(v, 0); + f64[1] = Float64x2.extractLane(v, 1); + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[1, -2], [-3, 4], [Infinity, -Infinity], [NaN, -0]]; + + for (var v of vals) { + var f = Float64x2(...v); + assertEqX8(Uint16x8.fromFloat64x2Bits(f), expected(f, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromFloat64x2Bits(f), expected(f, SharedArrayBuffer)); + } +} + +function testUint16x8FromInt8x16Bits() { + function expected(v, Buffer) { + var i8 = new Int8Array(new Buffer(16)); + var u16 = new Uint16Array(i8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) i8[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[0, 1, -2, 3, -4, 5, INT8_MIN, INT8_MAX, -6, 7, -8, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Int8x16(...v); + assertEqX8(Uint16x8.fromInt8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromInt8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint16x8FromUint8x16Bits() { + function expected(v, Buffer) { + var u8 = new Uint8Array(new Buffer(16)); + var u16 = new Uint16Array(u8.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 16; i++) u8[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[0, 1, -2, 3, -4, UINT8_MAX, INT8_MIN, INT8_MAX, -6, 7, -8, 9, -10, 11, -12, 13]]; + + for (var v of vals) { + var i = Uint8x16(...v); + assertEqX8(Uint16x8.fromUint8x16Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromUint8x16Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint16x8FromInt16x8Bits() { + function expected(v, Buffer) { + var i16 = new Int16Array(new Buffer(16)); + var u16 = new Uint16Array(i16.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 8; i++) i16[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[1, 2, 3, 4, 5, 6, 7, 8], + [INT16_MIN, UINT16_MAX, INT16_MAX, UINT16_MAX, 0, UINT16_MAX, 0, UINT16_MAX]]; + + for (var v of vals) { + var i = Int16x8(...v); + assertEqX8(Uint16x8.fromInt16x8Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromInt16x8Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint16x8FromInt32x4Bits() { + function expected(v, Buffer) { + var i32 = new Int32Array(new Buffer(16)); + var u16 = new Uint16Array(i32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) i32[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[1, -2, -3, 4], [INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN]]; + + for (var v of vals) { + var i = Int32x4(...v); + assertEqX8(Uint16x8.fromInt32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromInt32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function testUint16x8FromUint32x4Bits() { + function expected(v, Buffer) { + var u32 = new Uint32Array(new Buffer(16)); + var u16 = new Uint16Array(u32.buffer); + var asArr = simdToArray(v); + for (var i = 0; i < 4; i++) u32[i] = asArr[i]; + return [u16[0], u16[1], u16[2], u16[3], u16[4], u16[5], u16[6], u16[7]]; + } + + var vals = [[1, -2, -3, 4], [INT16_MAX, INT16_MIN, INT32_MAX, INT32_MIN]]; + + for (var v of vals) { + var i = Uint32x4(...v); + assertEqX8(Uint16x8.fromUint32x4Bits(i), expected(i, ArrayBuffer)); + if (typeof SharedArrayBuffer != "undefined") + assertEqX8(Uint16x8.fromUint32x4Bits(i), expected(i, SharedArrayBuffer)); + } +} + +function test() { + testFloat32x4FromFloat64x2Bits(); + testFloat32x4FromInt8x16Bits(); + testFloat32x4FromInt16x8Bits(); + testFloat32x4FromInt32x4(); + testFloat32x4FromInt32x4Bits(); + testFloat32x4FromUint8x16Bits(); + testFloat32x4FromUint16x8Bits(); + testFloat32x4FromUint32x4(); + testFloat32x4FromUint32x4Bits(); + + testFloat64x2FromFloat32x4Bits(); + testFloat64x2FromInt8x16Bits(); + testFloat64x2FromInt16x8Bits(); + testFloat64x2FromInt32x4Bits(); + testFloat64x2FromUint8x16Bits(); + testFloat64x2FromUint16x8Bits(); + testFloat64x2FromUint32x4Bits(); + + testInt8x16FromFloat32x4Bits(); + testInt8x16FromFloat64x2Bits(); + testInt8x16FromInt16x8Bits(); + testInt8x16FromInt32x4Bits(); + testInt8x16FromUint8x16Bits(); + testInt8x16FromUint16x8Bits(); + testInt8x16FromUint32x4Bits(); + + testInt16x8FromFloat32x4Bits(); + testInt16x8FromFloat64x2Bits(); + testInt16x8FromInt8x16Bits(); + testInt16x8FromInt32x4Bits(); + testInt16x8FromUint8x16Bits(); + testInt16x8FromUint16x8Bits(); + testInt16x8FromUint32x4Bits(); + + testInt32x4FromFloat32x4(); + testInt32x4FromFloat32x4Bits(); + testInt32x4FromFloat64x2Bits(); + testInt32x4FromInt8x16Bits(); + testInt32x4FromInt16x8Bits(); + testInt32x4FromUint8x16Bits(); + testInt32x4FromUint16x8Bits(); + testInt32x4FromUint32x4Bits(); + + testUint8x16FromFloat32x4Bits(); + testUint8x16FromFloat64x2Bits(); + testUint8x16FromInt8x16Bits(); + testUint8x16FromInt16x8Bits(); + testUint8x16FromInt32x4Bits(); + testUint8x16FromUint16x8Bits(); + testUint8x16FromUint32x4Bits(); + + testUint16x8FromFloat32x4Bits(); + testUint16x8FromFloat64x2Bits(); + testUint16x8FromInt8x16Bits(); + testUint16x8FromInt16x8Bits(); + testUint16x8FromInt32x4Bits(); + testUint16x8FromUint8x16Bits(); + testUint16x8FromUint32x4Bits(); + + testUint32x4FromFloat32x4(); + testUint32x4FromFloat32x4Bits(); + testUint32x4FromFloat64x2Bits(); + testUint32x4FromInt8x16Bits(); + testUint32x4FromInt16x8Bits(); + testUint32x4FromInt32x4Bits(); + testUint32x4FromUint8x16Bits(); + testUint32x4FromUint16x8Bits(); + + if (typeof reportCompare === "function") { + reportCompare(true, true); + } +} + +test(); diff --git a/js/src/tests/non262/SIMD/float64x2-arithmetic.js b/js/src/tests/non262/SIMD/float64x2-arithmetic.js new file mode 100644 index 000000000000..17c51a9e0a1d --- /dev/null +++ b/js/src/tests/non262/SIMD/float64x2-arithmetic.js @@ -0,0 +1,69 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float64x2 = SIMD.Float64x2; + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +function add(a, b) { return a + b; } +function sub(a, b) { return a - b; } +function mul(a, b) { return a * b; } +function div(a, b) { return a / b; } +function neg(a) { return -a; } +function reciprocalApproximation(a) { return 1 / a; } +function reciprocalSqrtApproximation(a) { return 1 / Math.sqrt(a); } + +function testAdd(v, w) { + return testBinaryFunc(v, w, Float64x2.add, add); +} +function testSub(v, w) { + return testBinaryFunc(v, w, Float64x2.sub, sub); +} +function testMul(v, w) { + return testBinaryFunc(v, w, Float64x2.mul, mul); +} +function testDiv(v, w) { + return testBinaryFunc(v, w, Float64x2.div, div); +} +function testAbs(v) { + return testUnaryFunc(v, Float64x2.abs, Math.abs); +} +function testNeg(v) { + return testUnaryFunc(v, Float64x2.neg, neg); +} +function testReciprocalApproximation(v) { + return testUnaryFunc(v, Float64x2.reciprocalApproximation, reciprocalApproximation); +} +function testReciprocalSqrtApproximation(v) { + return testUnaryFunc(v, Float64x2.reciprocalSqrtApproximation, reciprocalSqrtApproximation); +} +function testSqrt(v) { + return testUnaryFunc(v, Float64x2.sqrt, Math.sqrt); +} + +function test() { + var v, w; + for ([v, w] of [[Float64x2(1, 2), Float64x2(3, 4)], + [Float64x2(1.894, 2.8909), Float64x2(100.764, 200.987)], + [Float64x2(-1, -2), Float64x2(-14.54, 57)], + [Float64x2(+Infinity, -Infinity), Float64x2(NaN, -0)], + [Float64x2(Math.pow(2, 31), Math.pow(2, -31)), Float64x2(Math.pow(2, -1047), Math.pow(2, -149))]]) + { + testAdd(v, w); + testSub(v, w); + testMul(v, w); + testDiv(v, w); + testAbs(v); + testNeg(v); + testReciprocalApproximation(v); + testSqrt(v); + testReciprocalSqrtApproximation(v); + } + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); + diff --git a/js/src/tests/non262/SIMD/load-floats.js b/js/src/tests/non262/SIMD/load-floats.js new file mode 100644 index 000000000000..9ae52dc8ccb2 --- /dev/null +++ b/js/src/tests/non262/SIMD/load-floats.js @@ -0,0 +1,19 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { testLoad } = Helpers; + +testLoad('Float32x4', new Float32Array(SIZE_32_ARRAY)); +testLoad('Float64x2', new Float64Array(SIZE_64_ARRAY)); + +if (typeof SharedArrayBuffer != "undefined") { + testLoad('Float32x4', new Float32Array(new SharedArrayBuffer(SIZE_8_ARRAY))); + testLoad('Float64x2', new Float64Array(new SharedArrayBuffer(SIZE_8_ARRAY))); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/load-int16x8.js b/js/src/tests/non262/SIMD/load-int16x8.js new file mode 100644 index 000000000000..a8b90ca1586a --- /dev/null +++ b/js/src/tests/non262/SIMD/load-int16x8.js @@ -0,0 +1,17 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { testLoad } = Helpers; + +testLoad('Int16x8', new Int16Array(SIZE_16_ARRAY)); + +if (typeof SharedArrayBuffer != "undefined") { + testLoad('Int16x8', new Int16Array(new SharedArrayBuffer(SIZE_8_ARRAY))); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/load-int32x4.js b/js/src/tests/non262/SIMD/load-int32x4.js new file mode 100644 index 000000000000..0719cbc17635 --- /dev/null +++ b/js/src/tests/non262/SIMD/load-int32x4.js @@ -0,0 +1,18 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { testLoad } = Helpers; + +testLoad('Int32x4', new Int32Array(SIZE_32_ARRAY)); + +if (typeof SharedArrayBuffer != "undefined") { + testLoad('Int32x4', new Int32Array(new SharedArrayBuffer(SIZE_8_ARRAY))); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); + diff --git a/js/src/tests/non262/SIMD/load-int8x16.js b/js/src/tests/non262/SIMD/load-int8x16.js new file mode 100644 index 000000000000..cb6c24285fd3 --- /dev/null +++ b/js/src/tests/non262/SIMD/load-int8x16.js @@ -0,0 +1,17 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { testLoad } = Helpers; + +testLoad('Int8x16', new Int8Array(SIZE_8_ARRAY)); + +if (typeof SharedArrayBuffer != "undefined") { + testLoad('Int8x16', new Int8Array(new SharedArrayBuffer(SIZE_8_ARRAY))); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/load-sab-buffer-compat.js b/js/src/tests/non262/SIMD/load-sab-buffer-compat.js new file mode 100644 index 000000000000..fe8fac3ede95 --- /dev/null +++ b/js/src/tests/non262/SIMD/load-sab-buffer-compat.js @@ -0,0 +1,49 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { MakeComparator } = Helpers; + +function testSharedArrayBufferCompat() { + var TA = new Float32Array(new SharedArrayBuffer(16*4)); + for (var i = 0; i < 16; i++) + TA[i] = i + 1; + + for (var ta of [ + new Uint8Array(TA.buffer), + new Int8Array(TA.buffer), + new Uint16Array(TA.buffer), + new Int16Array(TA.buffer), + new Uint32Array(TA.buffer), + new Int32Array(TA.buffer), + new Float32Array(TA.buffer), + new Float64Array(TA.buffer) + ]) + { + for (var kind of ['Int32x4', 'Uint32x4', 'Float32x4', 'Float64x2']) { + var comp = MakeComparator(kind, ta); + comp.load(0); + comp.load1(0); + comp.load2(0); + comp.load3(0); + + comp.load(3); + comp.load1(3); + comp.load2(3); + comp.load3(3); + } + + assertThrowsInstanceOf(() => SIMD.Int32x4.load(ta, 1024), RangeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.load(ta, 1024), RangeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.load(ta, 1024), RangeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.load(ta, 1024), RangeError); + } +} + +testSharedArrayBufferCompat(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/load-unsigned-integers.js b/js/src/tests/non262/SIMD/load-unsigned-integers.js new file mode 100644 index 000000000000..381e7b60a119 --- /dev/null +++ b/js/src/tests/non262/SIMD/load-unsigned-integers.js @@ -0,0 +1,22 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var { testLoad } = Helpers; + +testLoad('Uint8x16', new Uint8Array(SIZE_8_ARRAY)); +testLoad('Uint16x8', new Uint16Array(SIZE_16_ARRAY)); +testLoad('Uint32x4', new Uint32Array(SIZE_32_ARRAY)); + +if (typeof SharedArrayBuffer != "undefined") { + testLoad('Uint8x16', new Uint8Array(new SharedArrayBuffer(SIZE_8_ARRAY))); + testLoad('Uint16x8', new Uint16Array(new SharedArrayBuffer(SIZE_8_ARRAY))); + testLoad('Uint32x4', new Uint32Array(new SharedArrayBuffer(SIZE_8_ARRAY))); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); + diff --git a/js/src/tests/non262/SIMD/minmax.js b/js/src/tests/non262/SIMD/minmax.js new file mode 100644 index 000000000000..9742b70d1db9 --- /dev/null +++ b/js/src/tests/non262/SIMD/minmax.js @@ -0,0 +1,85 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; + +function testMaxFloat32(v, w) { + return testBinaryFunc(v, w, Float32x4.max, (x, y) => Math.fround(Math.max(x, y)), 4); +} +function testMinFloat32(v, w) { + return testBinaryFunc(v, w, Float32x4.min, (x, y) => Math.fround(Math.min(x, y)), 4); +} + +function testMaxFloat64(v, w) { + return testBinaryFunc(v, w, Float64x2.max, (x, y) => Math.max(x, y), 2); +} +function testMinFloat64(v, w) { + return testBinaryFunc(v, w, Float64x2.min, (x, y) => Math.min(x, y), 2); +} + +function maxNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.max(x, y); +} + +function minNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.min(x, y); +} + +function testMaxNumFloat32(v, w) { + return testBinaryFunc(v, w, Float32x4.maxNum, maxNum, 4); +} +function testMinNumFloat32(v, w) { + return testBinaryFunc(v, w, Float32x4.minNum, minNum, 4); +} + +function testMaxNumFloat64(v, w) { + return testBinaryFunc(v, w, Float64x2.maxNum, maxNum, 2); +} +function testMinNumFloat64(v, w) { + return testBinaryFunc(v, w, Float64x2.minNum, minNum, 2); +} + +function test() { + var v, w; + for ([v, w] of [[Float32x4(1, 20, 30, 4), Float32x4(10, 2, 3, 40)], + [Float32x4(9.999, 2.1234, 30.4443, 4), Float32x4(10, 2.1233, 30.4444, 4.0001)], + [Float32x4(NaN, -Infinity, +Infinity, -0), Float32x4(13.37, 42.42, NaN, 0)]]) + { + testMinFloat32(v, w); + testMaxFloat32(v, w); + testMinNumFloat32(v, w); + testMaxNumFloat32(v, w); + } + + for ([v, w] of [[Float64x2(1, 20), Float64x2(10, 2)], + [Float64x2(30, 4), Float64x2(3, 40)], + [Float64x2(9.999, 2.1234), Float64x2(10, 2.1233)], + [Float64x2(30.4443, 4), Float64x2(30.4444, 4.0001)], + [Float64x2(NaN, -Infinity), Float64x2(13.37, 42.42)], + [Float64x2(+Infinity, -0), Float64x2(NaN, 0)]]) + { + testMinFloat64(v, w); + testMaxFloat64(v, w); + testMinNumFloat64(v, w); + testMaxNumFloat64(v, w); + } + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); + diff --git a/js/src/tests/non262/SIMD/replaceLane.js b/js/src/tests/non262/SIMD/replaceLane.js new file mode 100644 index 000000000000..b285dc27179c --- /dev/null +++ b/js/src/tests/non262/SIMD/replaceLane.js @@ -0,0 +1,226 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +function replaceLaneN(laneIndex, arr, value) { + var copy = arr.slice(); + assertEq(laneIndex <= arr.length, true); + copy[laneIndex] = value; + return copy; +} + +var replaceLane0 = replaceLaneN.bind(null, 0); +var replaceLane1 = replaceLaneN.bind(null, 1); +var replaceLane2 = replaceLaneN.bind(null, 2); +var replaceLane3 = replaceLaneN.bind(null, 3); +var replaceLane4 = replaceLaneN.bind(null, 4); +var replaceLane5 = replaceLaneN.bind(null, 5); +var replaceLane6 = replaceLaneN.bind(null, 6); +var replaceLane7 = replaceLaneN.bind(null, 7); +var replaceLane8 = replaceLaneN.bind(null, 8); +var replaceLane9 = replaceLaneN.bind(null, 9); +var replaceLane10 = replaceLaneN.bind(null, 10); +var replaceLane11 = replaceLaneN.bind(null, 11); +var replaceLane12 = replaceLaneN.bind(null, 12); +var replaceLane13 = replaceLaneN.bind(null, 13); +var replaceLane14 = replaceLaneN.bind(null, 14); +var replaceLane15 = replaceLaneN.bind(null, 15); + +function testReplaceLane(vec, scalar, simdFunc, func) { + var varr = simdToArray(vec); + var observed = simdToArray(simdFunc(vec, scalar)); + var expected = func(varr, scalar); + for (var i = 0; i < observed.length; i++) + assertEq(observed[i], expected[i]); +} + +function test() { + function testType(type, inputs) { + var length = simdToArray(inputs[0][0]).length; + for (var [vec, s] of inputs) { + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 0, y), replaceLane0); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 1, y), replaceLane1); + if (length <= 2) + continue; + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 2, y), replaceLane2); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 3, y), replaceLane3); + if (length <= 4) + continue; + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 4, y), replaceLane4); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 5, y), replaceLane5); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 6, y), replaceLane6); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 7, y), replaceLane7); + if (length <= 8) + continue; + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 8, y), replaceLane8); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 9, y), replaceLane9); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 10, y), replaceLane10); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 11, y), replaceLane11); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 12, y), replaceLane12); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 13, y), replaceLane13); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 14, y), replaceLane14); + testReplaceLane(vec, s, (x,y) => SIMD[type].replaceLane(x, 15, y), replaceLane15); + } + } + + function TestError(){}; + var good = {valueOf: () => 42}; + var bad = {valueOf: () => {throw new TestError(); }}; + + var Float32x4inputs = [ + [Float32x4(1, 2, 3, 4), 5], + [Float32x4(1.87, 2.08, 3.84, 4.17), Math.fround(13.37)], + [Float32x4(NaN, -0, Infinity, -Infinity), 0] + ]; + testType('Float32x4', Float32x4inputs); + + var v = Float32x4inputs[1][0]; + assertEqX4(Float32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), NaN)); + assertEqX4(Float32x4.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Float32x4.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Float32x4.replaceLane(v, 4, good), RangeError); + assertThrowsInstanceOf(() => Float32x4.replaceLane(v, 1.1, good), RangeError); + + var Float64x2inputs = [ + [Float64x2(1, 2), 5], + [Float64x2(1.87, 2.08), Math.fround(13.37)], + [Float64x2(NaN, -0), 0] + ]; + testType('Float64x2', Float64x2inputs); + + var v = Float64x2inputs[1][0]; + assertEqX2(Float64x2.replaceLane(v, 0), replaceLane0(simdToArray(v), NaN)); + assertEqX2(Float64x2.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Float64x2.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Float64x2.replaceLane(v, 2, good), RangeError); + assertThrowsInstanceOf(() => Float64x2.replaceLane(v, 1.1, good), RangeError); + + var Int8x16inputs = [[Int8x16(0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, INT8_MIN, INT8_MAX), 17]]; + testType('Int8x16', Int8x16inputs); + + var v = Int8x16inputs[0][0]; + assertEqX16(Int8x16.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX16(Int8x16.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Int8x16.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Int8x16.replaceLane(v, 16, good), RangeError); + assertThrowsInstanceOf(() => Int8x16.replaceLane(v, 1.1, good), RangeError); + + var Int16x8inputs = [[Int16x8(0, 1, 2, 3, -1, -2, INT16_MIN, INT16_MAX), 9]]; + testType('Int16x8', Int16x8inputs); + + var v = Int16x8inputs[0][0]; + assertEqX8(Int16x8.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX8(Int16x8.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Int16x8.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Int16x8.replaceLane(v, 8, good), RangeError); + assertThrowsInstanceOf(() => Int16x8.replaceLane(v, 1.1, good), RangeError); + + var Int32x4inputs = [ + [Int32x4(1, 2, 3, 4), 5], + [Int32x4(INT32_MIN, INT32_MAX, 3, 4), INT32_MIN], + ]; + testType('Int32x4', Int32x4inputs); + + var v = Int32x4inputs[1][0]; + assertEqX4(Int32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX4(Int32x4.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Int32x4.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Int32x4.replaceLane(v, 4, good), RangeError); + assertThrowsInstanceOf(() => Int32x4.replaceLane(v, 1.1, good), RangeError); + + var Uint8x16inputs = [[Uint8x16(0, 1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, INT8_MIN, UINT8_MAX), 17]]; + testType('Uint8x16', Uint8x16inputs); + + var v = Uint8x16inputs[0][0]; + assertEqX16(Uint8x16.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX16(Uint8x16.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Uint8x16.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Uint8x16.replaceLane(v, 16, good), RangeError); + assertThrowsInstanceOf(() => Uint8x16.replaceLane(v, 1.1, good), RangeError); + + var Uint16x8inputs = [[Uint16x8(0, 1, 2, 3, -1, -2, INT16_MIN, UINT16_MAX), 9]]; + testType('Uint16x8', Uint16x8inputs); + + var v = Uint16x8inputs[0][0]; + assertEqX8(Uint16x8.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX8(Uint16x8.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Uint16x8.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Uint16x8.replaceLane(v, 8, good), RangeError); + assertThrowsInstanceOf(() => Uint16x8.replaceLane(v, 1.1, good), RangeError); + + var Uint32x4inputs = [ + [Uint32x4(1, 2, 3, 4), 5], + [Uint32x4(INT32_MIN, UINT32_MAX, INT32_MAX, 4), UINT32_MAX], + ]; + testType('Uint32x4', Uint32x4inputs); + + var v = Uint32x4inputs[1][0]; + assertEqX4(Uint32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), 0)); + assertEqX4(Uint32x4.replaceLane(v, 0, good), replaceLane0(simdToArray(v), good | 0)); + assertThrowsInstanceOf(() => Uint32x4.replaceLane(v, 0, bad), TestError); + assertThrowsInstanceOf(() => Uint32x4.replaceLane(v, 4, good), RangeError); + assertThrowsInstanceOf(() => Uint32x4.replaceLane(v, 1.1, good), RangeError); + + var Bool64x2inputs = [ + [Bool64x2(true, true), false], + ]; + testType('Bool64x2', Bool64x2inputs); + + var v = Bool64x2inputs[0][0]; + assertEqX2(Bool64x2.replaceLane(v, 0), replaceLane0(simdToArray(v), false)); + assertEqX2(Bool64x2.replaceLane(v, 0, true), replaceLane0(simdToArray(v), true)); + assertEqX2(Bool64x2.replaceLane(v, 0, bad), replaceLane0(simdToArray(v), true)); + assertThrowsInstanceOf(() => Bool64x2.replaceLane(v, 4, true), RangeError); + assertThrowsInstanceOf(() => Bool64x2.replaceLane(v, 1.1, false), RangeError); + + var Bool32x4inputs = [ + [Bool32x4(true, true, true, true), false], + ]; + testType('Bool32x4', Bool32x4inputs); + + var v = Bool32x4inputs[0][0]; + assertEqX4(Bool32x4.replaceLane(v, 0), replaceLane0(simdToArray(v), false)); + assertEqX4(Bool32x4.replaceLane(v, 0, true), replaceLane0(simdToArray(v), true)); + assertEqX4(Bool32x4.replaceLane(v, 0, bad), replaceLane0(simdToArray(v), true)); + assertThrowsInstanceOf(() => Bool32x4.replaceLane(v, 4, true), RangeError); + assertThrowsInstanceOf(() => Bool32x4.replaceLane(v, 1.1, false), RangeError); + + var Bool16x8inputs = [ + [Bool16x8(true, true, true, true, true, true, true, true), false], + ]; + + testType('Bool16x8', Bool16x8inputs); + var v = Bool16x8inputs[0][0]; + assertEqX8(Bool16x8.replaceLane(v, 0), replaceLane0(simdToArray(v), false)); + assertEqX8(Bool16x8.replaceLane(v, 0, true), replaceLane0(simdToArray(v), true)); + assertEqX8(Bool16x8.replaceLane(v, 0, bad), replaceLane0(simdToArray(v), true)); + assertThrowsInstanceOf(() => Bool16x8.replaceLane(v, 16, true), RangeError); + assertThrowsInstanceOf(() => Bool16x8.replaceLane(v, 1.1, false), RangeError); + + var Bool8x16inputs = [ + [Bool8x16(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true), false], + ]; + + testType('Bool8x16', Bool8x16inputs); + var v = Bool8x16inputs[0][0]; + assertEqX16(Bool8x16.replaceLane(v, 0), replaceLane0(simdToArray(v), false)); + assertEqX16(Bool8x16.replaceLane(v, 0, true), replaceLane0(simdToArray(v), true)); + assertEqX16(Bool8x16.replaceLane(v, 0, bad), replaceLane0(simdToArray(v), true)); + assertThrowsInstanceOf(() => Bool8x16.replaceLane(v, 16, true), RangeError); + assertThrowsInstanceOf(() => Bool8x16.replaceLane(v, 1.1, false), RangeError); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/select-bitselect.js b/js/src/tests/non262/SIMD/select-bitselect.js new file mode 100644 index 000000000000..61fcd1a3f2fb --- /dev/null +++ b/js/src/tests/non262/SIMD/select-bitselect.js @@ -0,0 +1,133 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +function getMask(i, maskLength) { + var args = []; + for (var j = 0; j < maskLength; j++) + args.push((i >> j) & 1); + if (maskLength == 2) + return Bool64x2(...args); + else if (maskLength == 4) + return Bool32x4(...args); + else if (maskLength == 8) + return Bool16x8(...args); + else if (maskLength == 16) + return Bool8x16(...args); + else + throw new Error("Invalid mask length."); +} + +function select(mask, ifTrue, ifFalse) { + var m = simdToArray(mask); + var tv = simdToArray(ifTrue); + var fv = simdToArray(ifFalse); + return m.map(function(v, i) { + return (v ? tv : fv)[i]; + }); +} + +/** + * Tests type.select on all input pairs, for all possible masks. As the mask + * has 4 lanes (for Int32x4) and 2 possible values (true or false), there are 16 possible + * masks. For Int8x16, the mask has 16 lanes and 2 possible values, so there are 256 + * possible masks. For Int16x8, the mask has 8 lanes and 2 possible values, so there + * are 64 possible masks. + */ +function testSelect(type, inputs) { + var x, y; + var maskLength = simdLengthType(type); + for (var i = 0; i < Math.pow(maskLength, 2); i++) { + var mask = getMask(i, maskLength); + for ([x, y] of inputs) + assertEqVec(type.select(mask, x, y), select(mask, x, y)); + } +} + +function test() { + var inputs = [ + [Int8x16(0,4,9,16,25,36,49,64,81,121,-4,-9,-16,-25,-36,-49), Int8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)], + [Int8x16(-1, 2, INT8_MAX, INT8_MIN, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Int8x16(INT8_MAX, -4, INT8_MIN, 42, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)] + ]; + + testSelect(Int8x16, inputs); + + inputs = [ + [Int16x8(0,4,9,16,25,36,49,64), Int16x8(1,2,3,4,5,6,7,8)], + [Int16x8(-1, 2, INT16_MAX, INT16_MIN, 5, 6, 7, 8), + Int16x8(INT16_MAX, -4, INT16_MIN, 42, 5, 6, 7, 8)] + ]; + + testSelect(Int16x8, inputs); + + inputs = [ + [Int32x4(0,4,9,16), Int32x4(1,2,3,4)], + [Int32x4(-1, 2, INT32_MAX, INT32_MIN), Int32x4(INT32_MAX, -4, INT32_MIN, 42)] + ]; + + testSelect(Int32x4, inputs); + + inputs = [ + [Uint8x16(0,4,9,16,25,36,49,64,81,121,-4,-9,-16,-25,-36,-49), Uint8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)], + [Uint8x16(-1, 2, INT8_MAX, INT8_MIN, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Uint8x16(INT8_MAX, -4, INT8_MIN, 42, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)] + ]; + + testSelect(Uint8x16, inputs); + + inputs = [ + [Uint16x8(0,4,9,16,25,36,49,64), Uint16x8(1,2,3,4,5,6,7,8)], + [Uint16x8(-1, 2, INT16_MAX, INT16_MIN, 5, 6, 7, 8), + Uint16x8(INT16_MAX, -4, INT16_MIN, 42, 5, 6, 7, 8)] + ]; + + testSelect(Uint16x8, inputs); + + inputs = [ + [Uint32x4(0,4,9,16), Uint32x4(1,2,3,4)], + [Uint32x4(-1, 2, INT32_MAX, INT32_MIN), Uint32x4(INT32_MAX, -4, INT32_MIN, 42)] + ]; + + testSelect(Uint32x4, inputs); + + inputs = [ + [Float32x4(0.125,4.25,9.75,16.125), Float32x4(1.5,2.75,3.25,4.5)], + [Float32x4(-1.5,-0,NaN,-Infinity), Float32x4(1,-2,13.37,3.13)], + [Float32x4(1.5,2.75,NaN,Infinity), Float32x4(-NaN,-Infinity,9.75,16.125)] + ]; + + testSelect(Float32x4, inputs); + + inputs = [ + [Float64x2(0.125,4.25), Float64x2(9.75,16.125)], + [Float64x2(1.5,2.75), Float64x2(3.25,4.5)], + [Float64x2(-1.5,-0), Float64x2(NaN,-Infinity)], + [Float64x2(1,-2), Float64x2(13.37,3.13)], + [Float64x2(1.5,2.75), Float64x2(NaN,Infinity)], + [Float64x2(-NaN,-Infinity), Float64x2(9.75,16.125)] + ]; + + testSelect(Float64x2, inputs); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/shell.js b/js/src/tests/non262/SIMD/shell.js new file mode 100644 index 000000000000..943965390e43 --- /dev/null +++ b/js/src/tests/non262/SIMD/shell.js @@ -0,0 +1,580 @@ +function makeFloat(sign, exp, mantissa) { + assertEq(sign, sign & 0x1); + assertEq(exp, exp & 0xFF); + assertEq(mantissa, mantissa & 0x7FFFFF); + + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + + i32[0] = (sign << 31) | (exp << 23) | mantissa; + return f32[0]; +} + +function makeDouble(sign, exp, mantissa) { + assertEq(sign, sign & 0x1); + assertEq(exp, exp & 0x7FF); + + // Can't use bitwise operations on mantissa, as it might be a double + assertEq(mantissa <= 0xfffffffffffff, true); + var highBits = (mantissa / Math.pow(2, 32)) | 0; + var lowBits = mantissa - highBits * Math.pow(2, 32); + + var i32 = new Int32Array(2); + var f64 = new Float64Array(i32.buffer); + + // Note that this assumes little-endian order, which is the case on tier-1 + // platforms. + i32[0] = lowBits; + i32[1] = (sign << 31) | (exp << 20) | highBits; + return f64[0]; +} + +function GetType(v) { + switch (Object.getPrototypeOf(v)) { + case SIMD.Int8x16.prototype: return SIMD.Int8x16; + case SIMD.Int16x8.prototype: return SIMD.Int16x8; + case SIMD.Int32x4.prototype: return SIMD.Int32x4; + case SIMD.Uint8x16.prototype: return SIMD.Uint8x16; + case SIMD.Uint16x8.prototype: return SIMD.Uint16x8; + case SIMD.Uint32x4.prototype: return SIMD.Uint32x4; + case SIMD.Float32x4.prototype: return SIMD.Float32x4; + case SIMD.Float64x2.prototype: return SIMD.Float64x2; + case SIMD.Bool8x16.prototype: return SIMD.Bool8x16; + case SIMD.Bool16x8.prototype: return SIMD.Bool16x8; + case SIMD.Bool32x4.prototype: return SIMD.Bool32x4; + case SIMD.Bool64x2.prototype: return SIMD.Bool64x2; + } +} + +function assertEqFloat64x2(v, arr) { + try { + assertEq(SIMD.Float64x2.extractLane(v, 0), arr[0]); + assertEq(SIMD.Float64x2.extractLane(v, 1), arr[1]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqBool64x2(v, arr) { + try { + assertEq(SIMD.Bool64x2.extractLane(v, 0), arr[0]); + assertEq(SIMD.Bool64x2.extractLane(v, 1), arr[1]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqX2(v, arr) { + var Type = GetType(v); + if (Type === SIMD.Float64x2) assertEqFloat64x2(v, arr); + else if (Type === SIMD.Bool64x2) assertEqBool64x2(v, arr); + else throw new TypeError("Unknown SIMD kind."); +} + +function assertEqInt32x4(v, arr) { + try { + for (var i = 0; i < 4; i++) + assertEq(SIMD.Int32x4.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqUint32x4(v, arr) { + try { + for (var i = 0; i < 4; i++) + assertEq(SIMD.Uint32x4.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqFloat32x4(v, arr) { + try { + for (var i = 0; i < 4; i++) + assertEq(SIMD.Float32x4.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqBool32x4(v, arr) { + try { + for (var i = 0; i < 4; i++) + assertEq(SIMD.Bool32x4.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqX4(v, arr) { + var Type = GetType(v); + if (Type === SIMD.Int32x4) assertEqInt32x4(v, arr); + else if (Type === SIMD.Uint32x4) assertEqUint32x4(v, arr); + else if (Type === SIMD.Float32x4) assertEqFloat32x4(v, arr); + else if (Type === SIMD.Bool32x4) assertEqBool32x4(v, arr); + else throw new TypeError("Unknown SIMD kind."); +} + +function assertEqInt16x8(v, arr) { + try { + for (var i = 0; i < 8; i++) + assertEq(SIMD.Int16x8.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqUint16x8(v, arr) { + try { + for (var i = 0; i < 8; i++) + assertEq(SIMD.Uint16x8.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqBool16x8(v, arr) { + try { + for (var i = 0; i < 8; i++){ + assertEq(SIMD.Bool16x8.extractLane(v, i), arr[i]); + } + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqX8(v, arr) { + var Type = GetType(v); + if (Type === SIMD.Int16x8) assertEqInt16x8(v, arr); + else if (Type === SIMD.Uint16x8) assertEqUint16x8(v, arr); + else if (Type === SIMD.Bool16x8) assertEqBool16x8(v, arr); + else throw new TypeError("Unknown x8 vector."); +} + +function assertEqInt8x16(v, arr) { + try { + for (var i = 0; i < 16; i++) + assertEq(SIMD.Int8x16.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqUint8x16(v, arr) { + try { + for (var i = 0; i < 16; i++) + assertEq(SIMD.Uint8x16.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqBool8x16(v, arr) { + try { + for (var i = 0; i < 16; i++) + assertEq(SIMD.Bool8x16.extractLane(v, i), arr[i]); + } catch (e) { + print("stack trace:", e.stack); + throw e; + } +} + +function assertEqX16(v, arr) { + var Type = GetType(v); + if (Type === SIMD.Int8x16) assertEqInt8x16(v, arr); + else if (Type === SIMD.Uint8x16) assertEqUint8x16(v, arr); + else if (Type === SIMD.Bool8x16) assertEqBool8x16(v, arr); + else throw new TypeError("Unknown x16 vector."); +} + +function simdLength(v) { + var pt = Object.getPrototypeOf(v); + if (pt == SIMD.Int8x16.prototype || pt == SIMD.Uint8x16.prototype || + pt === SIMD.Bool8x16.prototype) + return 16; + if (pt == SIMD.Int16x8.prototype || pt == SIMD.Uint16x8.prototype || + pt === SIMD.Bool16x8.prototype) + return 8; + if (pt === SIMD.Int32x4.prototype || pt === SIMD.Uint32x4.prototype || + pt === SIMD.Float32x4.prototype || pt === SIMD.Bool32x4.prototype) + return 4; + if (pt === SIMD.Float64x2.prototype || pt == SIMD.Bool64x2.prototype) + return 2; + throw new TypeError("Unknown SIMD kind."); +} + +function simdLengthType(t) { + if (t == SIMD.Int8x16 || t == SIMD.Uint8x16 || t == SIMD.Bool8x16) + return 16; + else if (t == SIMD.Int16x8 || t == SIMD.Uint16x8 || t == SIMD.Bool16x8) + return 8; + else if (t == SIMD.Int32x4 || t == SIMD.Uint32x4 || t == SIMD.Float32x4 || t == SIMD.Bool32x4) + return 4; + else if (t == SIMD.Float64x2 || t == SIMD.Bool64x2) + return 2; + else + throw new TypeError("Unknown SIMD kind."); +} + +function getAssertFuncFromLength(l) { + if (l == 2) + return assertEqX2; + else if (l == 4) + return assertEqX4; + else if (l == 8) + return assertEqX8; + else if (l == 16) + return assertEqX16; + else + throw new TypeError("Unknown SIMD kind."); +} + +function assertEqVec(v, arr) { + var Type = GetType(v); + if (Type === SIMD.Int8x16) assertEqInt8x16(v, arr); + else if (Type === SIMD.Int16x8) assertEqInt16x8(v, arr); + else if (Type === SIMD.Int32x4) assertEqInt32x4(v, arr); + else if (Type === SIMD.Uint8x16) assertEqUint8x16(v, arr); + else if (Type === SIMD.Uint16x8) assertEqUint16x8(v, arr); + else if (Type === SIMD.Uint32x4) assertEqUint32x4(v, arr); + else if (Type === SIMD.Float32x4) assertEqFloat32x4(v, arr); + else if (Type === SIMD.Float64x2) assertEqFloat64x2(v, arr); + else if (Type === SIMD.Bool8x16) assertEqBool8x16(v, arr); + else if (Type === SIMD.Bool16x8) assertEqBool16x8(v, arr); + else if (Type === SIMD.Bool32x4) assertEqBool32x4(v, arr); + else if (Type === SIMD.Bool64x2) assertEqBool64x2(v, arr); + else throw new TypeError("Unknown SIMD Kind"); +} + +function simdToArray(v) { + var Type = GetType(v); + + function indexes(n) { + var arr = []; + for (var i = 0; i < n; i++) arr.push(i); + return arr; + } + + if (Type === SIMD.Bool8x16) { + return indexes(16).map((i) => SIMD.Bool8x16.extractLane(v, i)); + } + + if (Type === SIMD.Bool16x8) { + return indexes(8).map((i) => SIMD.Bool16x8.extractLane(v, i)); + } + + if (Type === SIMD.Bool32x4) { + return indexes(4).map((i) => SIMD.Bool32x4.extractLane(v, i)); + } + + if (Type === SIMD.Bool64x2) { + return indexes(2).map((i) => SIMD.Bool64x2.extractLane(v, i)); + } + + if (Type === SIMD.Int8x16) { + return indexes(16).map((i) => SIMD.Int8x16.extractLane(v, i)); + } + + if (Type === SIMD.Int16x8) { + return indexes(8).map((i) => SIMD.Int16x8.extractLane(v, i)); + } + + if (Type === SIMD.Int32x4) { + return indexes(4).map((i) => SIMD.Int32x4.extractLane(v, i)); + } + + if (Type === SIMD.Uint8x16) { + return indexes(16).map((i) => SIMD.Uint8x16.extractLane(v, i)); + } + + if (Type === SIMD.Uint16x8) { + return indexes(8).map((i) => SIMD.Uint16x8.extractLane(v, i)); + } + + if (Type === SIMD.Uint32x4) { + return indexes(4).map((i) => SIMD.Uint32x4.extractLane(v, i)); + } + + if (Type === SIMD.Float32x4) { + return indexes(4).map((i) => SIMD.Float32x4.extractLane(v, i)); + } + + if (Type === SIMD.Float64x2) { + return indexes(2).map((i) => SIMD.Float64x2.extractLane(v, i)); + } + + throw new TypeError("Unknown SIMD Kind"); +} + +const INT8_MAX = Math.pow(2, 7) -1; +const INT8_MIN = -Math.pow(2, 7); +assertEq((INT8_MAX + 1) << 24 >> 24, INT8_MIN); +const INT16_MAX = Math.pow(2, 15) - 1; +const INT16_MIN = -Math.pow(2, 15); +assertEq((INT16_MAX + 1) << 16 >> 16, INT16_MIN); +const INT32_MAX = Math.pow(2, 31) - 1; +const INT32_MIN = -Math.pow(2, 31); +assertEq(INT32_MAX + 1 | 0, INT32_MIN); + +const UINT8_MAX = Math.pow(2, 8) - 1; +const UINT16_MAX = Math.pow(2, 16) - 1; +const UINT32_MAX = Math.pow(2, 32) - 1; + +function testUnaryFunc(v, simdFunc, func) { + var varr = simdToArray(v); + + var observed = simdToArray(simdFunc(v)); + var expected = varr.map(function(v, i) { return func(varr[i]); }); + + for (var i = 0; i < observed.length; i++) + assertEq(observed[i], expected[i]); +} + +function testBinaryFunc(v, w, simdFunc, func) { + var varr = simdToArray(v); + var warr = simdToArray(w); + + var observed = simdToArray(simdFunc(v, w)); + var expected = varr.map(function(v, i) { return func(varr[i], warr[i]); }); + + for (var i = 0; i < observed.length; i++) + assertEq(observed[i], expected[i]); +} + +function testBinaryCompare(v, w, simdFunc, func, outType) { + var varr = simdToArray(v); + var warr = simdToArray(w); + + var inLanes = simdLength(v); + var observed = simdToArray(simdFunc(v, w)); + var outTypeLen = simdLengthType(outType); + assertEq(observed.length, outTypeLen); + for (var i = 0; i < outTypeLen; i++) { + var j = ((i * inLanes) / outTypeLen) | 0; + assertEq(observed[i], func(varr[j], warr[j])); + } +} + +function testBinaryScalarFunc(v, scalar, simdFunc, func) { + var varr = simdToArray(v); + + var observed = simdToArray(simdFunc(v, scalar)); + var expected = varr.map(function(v, i) { return func(varr[i], scalar); }); + + for (var i = 0; i < observed.length; i++) + assertEq(observed[i], expected[i]); +} + +// Our array for Int32x4 and Float32x4 will have 16 elements +const SIZE_8_ARRAY = 64; +const SIZE_16_ARRAY = 32; +const SIZE_32_ARRAY = 16; +const SIZE_64_ARRAY = 8; + +const SIZE_BYTES = SIZE_32_ARRAY * 4; + +function MakeComparator(kind, arr, shared) { + var bpe = arr.BYTES_PER_ELEMENT; + var uint8 = (bpe != 1) ? new Uint8Array(arr.buffer) : arr; + + // Size in bytes of a single element in the SIMD vector. + var sizeOfLaneElem; + // Typed array constructor corresponding to the SIMD kind. + var typedArrayCtor; + switch (kind) { + case 'Int8x16': + sizeOfLaneElem = 1; + typedArrayCtor = Int8Array; + break; + case 'Int16x8': + sizeOfLaneElem = 2; + typedArrayCtor = Int16Array; + break; + case 'Int32x4': + sizeOfLaneElem = 4; + typedArrayCtor = Int32Array; + break; + case 'Uint8x16': + sizeOfLaneElem = 1; + typedArrayCtor = Uint8Array; + break; + case 'Uint16x8': + sizeOfLaneElem = 2; + typedArrayCtor = Uint16Array; + break; + case 'Uint32x4': + sizeOfLaneElem = 4; + typedArrayCtor = Uint32Array; + break; + case 'Float32x4': + sizeOfLaneElem = 4; + typedArrayCtor = Float32Array; + break; + case 'Float64x2': + sizeOfLaneElem = 8; + typedArrayCtor = Float64Array; + break; + default: + assertEq(true, false, "unknown SIMD kind"); + } + var lanes = 16 / sizeOfLaneElem; + // Reads (numElemToRead * sizeOfLaneElem) bytes in arr, and reinterprets + // these bytes as a typed array equivalent to the typed SIMD vector. + var slice = function(start, numElemToRead) { + // Read enough bytes + var startBytes = start * bpe; + var endBytes = startBytes + numElemToRead * sizeOfLaneElem; + var asArray = Array.prototype.slice.call(uint8, startBytes, endBytes); + + // If length is less than SIZE_BYTES bytes, fill with 0. + // This is needed for load1, load2, load3 which do only partial + // reads. + for (var i = asArray.length; i < SIZE_BYTES; i++) asArray[i] = 0; + assertEq(asArray.length, SIZE_BYTES); + + return new typedArrayCtor(new Uint8Array(asArray).buffer); + } + + var assertFunc = getAssertFuncFromLength(lanes); + var type = SIMD[kind]; + return { + load1: function(index) { + if (lanes >= 8) // Int8x16 and Int16x8 only support load, no load1/load2/etc. + return + var v = type.load1(arr, index); + assertFunc(v, slice(index, 1)); + }, + + load2: function(index) { + if (lanes !== 4) + return; + var v = type.load2(arr, index); + assertFunc(v, slice(index, 2)); + }, + + load3: function(index) { + if (lanes !== 4) + return; + var v = type.load3(arr, index); + assertFunc(v, slice(index, 3)); + }, + + load: function(index) { + var v = type.load(arr, index); + assertFunc(v, slice(index, lanes)); + } + } +} + +function testLoad(kind, TA) { + var lanes = TA.length / 4; + for (var i = TA.length; i--;) + TA[i] = i; + + for (var ta of [ + new Uint8Array(TA.buffer), + new Int8Array(TA.buffer), + new Uint16Array(TA.buffer), + new Int16Array(TA.buffer), + new Uint32Array(TA.buffer), + new Int32Array(TA.buffer), + new Float32Array(TA.buffer), + new Float64Array(TA.buffer) + ]) + { + // Invalid args + assertThrowsInstanceOf(() => SIMD[kind].load(), TypeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta), TypeError); + assertThrowsInstanceOf(() => SIMD[kind].load("hello", 0), TypeError); + // Indexes must be integers, there is no rounding. + assertThrowsInstanceOf(() => SIMD[kind].load(ta, 1.5), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, -1), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, "hello"), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, NaN), RangeError); + // Try to trip up the bounds checking. Int32 is enough for everybody. + assertThrowsInstanceOf(() => SIMD[kind].load(ta, 0x100000000), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, 0x80000000), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, 0x40000000), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, 0x20000000), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, (1<<30) * (1<<23) - 1), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load(ta, (1<<30) * (1<<23)), RangeError); + + // Valid and invalid reads + var C = MakeComparator(kind, ta); + var bpe = ta.BYTES_PER_ELEMENT; + + var lastValidArgLoad1 = (SIZE_BYTES - (16 / lanes)) / bpe | 0; + var lastValidArgLoad2 = (SIZE_BYTES - 8) / bpe | 0; + var lastValidArgLoad3 = (SIZE_BYTES - 12) / bpe | 0; + var lastValidArgLoad = (SIZE_BYTES - 16) / bpe | 0; + + C.load(0); + C.load(1); + C.load(2); + C.load(3); + C.load(lastValidArgLoad); + + C.load1(0); + C.load1(1); + C.load1(2); + C.load1(3); + C.load1(lastValidArgLoad1); + + C.load2(0); + C.load2(1); + C.load2(2); + C.load2(3); + C.load2(lastValidArgLoad2); + + C.load3(0); + C.load3(1); + C.load3(2); + C.load3(3); + C.load3(lastValidArgLoad3); + + assertThrowsInstanceOf(() => SIMD[kind].load(ta, lastValidArgLoad + 1), RangeError); + if (lanes <= 4) { + assertThrowsInstanceOf(() => SIMD[kind].load1(ta, lastValidArgLoad1 + 1), RangeError); + } + if (lanes == 4) { + assertThrowsInstanceOf(() => SIMD[kind].load2(ta, lastValidArgLoad2 + 1), RangeError); + assertThrowsInstanceOf(() => SIMD[kind].load3(ta, lastValidArgLoad3 + 1), RangeError); + } + + // Indexes are coerced with ToNumber. Try some strings that + // CanonicalNumericIndexString() would reject. + C.load("1.0e0"); + C.load(" 2"); + } + + if (lanes == 4) { + // Test ToNumber behavior. + var obj = { + valueOf: function() { return 12 } + } + var v = SIMD[kind].load(TA, obj); + assertEqX4(v, [12, 13, 14, 15]); + } + + var obj = { + valueOf: function() { throw new TypeError("i ain't a number"); } + } + assertThrowsInstanceOf(() => SIMD[kind].load(TA, obj), TypeError); +} + +var Helpers = { + testLoad, + MakeComparator +}; diff --git a/js/src/tests/non262/SIMD/shifts.js b/js/src/tests/non262/SIMD/shifts.js new file mode 100644 index 000000000000..1d81f7483a92 --- /dev/null +++ b/js/src/tests/non262/SIMD/shifts.js @@ -0,0 +1,202 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; + +// Int8 shifts. +function lsh8(a, b) { + return (a << (b & 7)) << 24 >> 24; +} +function rsha8(a, b) { + return (a >> (b & 7)) << 24 >> 24; +} +function rshl8(a, b) { + return (a >>> (b & 7)) << 24 >> 24; +} + +// Int16 shifts. +function lsh16(a, b) { + return (a << (b & 15)) << 16 >> 16; +} +function rsha16(a, b) { + return (a >> (b & 15)) << 16 >> 16; +} +function rshl16(a, b) { + return (a >>> (b & 15)) << 16 >> 16; +} + +// Int32 shifts. +function lsh32(a, b) { + return (a << (b & 31)) | 0; +} +function rsha32(a, b) { + return (a >> (b & 31)) | 0; +} +function rshl32(a, b) { + return (a >>> (b & 31)) | 0; +} + +// Uint8 shifts. +function ulsh8(a, b) { + return (a << (b & 7)) << 24 >>> 24; +} +function ursha8(a, b) { + return ((a << 24 >> 24) >> (b & 7)) << 24 >>> 24; +} +function urshl8(a, b) { + return (a >>> (b & 7)) << 24 >>> 24; +} + +// Uint16 shifts. +function ulsh16(a, b) { + return (a << (b & 15)) << 16 >>> 16; +} +function ursha16(a, b) { + return ((a << 16 >> 16) >> (b & 15)) << 16 >>> 16; +} +function urshl16(a, b) { + return (a >>> (b & 15)) << 16 >>> 16; +} + +// Uint32 shifts. +function ulsh32(a, b) { + return (a << (b & 31)) >>> 0; +} +function ursha32(a, b) { + return ((a | 0) >> (b & 31)) >>> 0; +} +function urshl32(a, b) { + return (a >>> (b & 31)) >>> 0; +} + +function test() { + function TestError() {}; + + var good = {valueOf: () => 21}; + var bad = {valueOf: () => {throw new TestError(); }}; + + for (var v of [ + Int8x16(-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16), + Int8x16(INT8_MAX, INT8_MIN, INT8_MAX - 1, INT8_MIN + 1) + ]) + { + for (var bits = -2; bits < 12; bits++) { + testBinaryScalarFunc(v, bits, Int8x16.shiftLeftByScalar, lsh8); + testBinaryScalarFunc(v, bits, Int8x16.shiftRightByScalar, rsha8); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Int8x16.shiftLeftByScalar, lsh8); + testBinaryScalarFunc(v, 3.5, Int8x16.shiftLeftByScalar, lsh8); + testBinaryScalarFunc(v, good, Int8x16.shiftLeftByScalar, lsh8); + } + for (var v of [ + Int16x8(-1, 2, -3, 4, -5, 6, -7, 8), + Int16x8(INT16_MAX, INT16_MIN, INT16_MAX - 1, INT16_MIN + 1) + ]) + { + for (var bits = -2; bits < 20; bits++) { + testBinaryScalarFunc(v, bits, Int16x8.shiftLeftByScalar, lsh16); + testBinaryScalarFunc(v, bits, Int16x8.shiftRightByScalar, rsha16); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Int16x8.shiftLeftByScalar, lsh16); + testBinaryScalarFunc(v, 3.5, Int16x8.shiftLeftByScalar, lsh16); + testBinaryScalarFunc(v, good, Int16x8.shiftLeftByScalar, lsh16); + } + for (var v of [ + Int32x4(-1, 2, -3, 4), + Int32x4(INT32_MAX, INT32_MIN, INT32_MAX - 1, INT32_MIN + 1) + ]) + { + for (var bits = -2; bits < 36; bits++) { + testBinaryScalarFunc(v, bits, Int32x4.shiftLeftByScalar, lsh32); + testBinaryScalarFunc(v, bits, Int32x4.shiftRightByScalar, rsha32); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Int32x4.shiftLeftByScalar, lsh32); + testBinaryScalarFunc(v, 3.5, Int32x4.shiftLeftByScalar, lsh32); + testBinaryScalarFunc(v, good, Int32x4.shiftLeftByScalar, lsh32); + } + + for (var v of [ + Uint8x16(-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16), + Uint8x16(INT8_MAX, INT8_MIN, INT8_MAX - 1, INT8_MIN + 1, UINT8_MAX, UINT8_MAX - 1) + ]) + { + for (var bits = -2; bits < 12; bits++) { + testBinaryScalarFunc(v, bits, Uint8x16.shiftLeftByScalar, ulsh8); + testBinaryScalarFunc(v, bits, Uint8x16.shiftRightByScalar, urshl8); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Uint8x16.shiftLeftByScalar, ulsh8); + testBinaryScalarFunc(v, 3.5, Uint8x16.shiftLeftByScalar, ulsh8); + testBinaryScalarFunc(v, good, Uint8x16.shiftLeftByScalar, ulsh8); + } + for (var v of [ + Uint16x8(-1, 2, -3, 4, -5, 6, -7, 8), + Uint16x8(INT16_MAX, INT16_MIN, INT16_MAX - 1, INT16_MIN + 1, UINT16_MAX, UINT16_MAX - 1) + ]) + { + for (var bits = -2; bits < 20; bits++) { + testBinaryScalarFunc(v, bits, Uint16x8.shiftLeftByScalar, ulsh16); + testBinaryScalarFunc(v, bits, Uint16x8.shiftRightByScalar, urshl16); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Uint16x8.shiftLeftByScalar, ulsh16); + testBinaryScalarFunc(v, 3.5, Uint16x8.shiftLeftByScalar, ulsh16); + testBinaryScalarFunc(v, good, Uint16x8.shiftLeftByScalar, ulsh16); + } + for (var v of [ + Uint32x4(-1, 2, -3, 4), + Uint32x4(UINT32_MAX, UINT32_MAX - 1, 0, 1), + Uint32x4(INT32_MAX, INT32_MIN, INT32_MAX - 1, INT32_MIN + 1) + ]) + { + for (var bits = -2; bits < 36; bits++) { + testBinaryScalarFunc(v, bits, Uint32x4.shiftLeftByScalar, ulsh32); + testBinaryScalarFunc(v, bits, Uint32x4.shiftRightByScalar, urshl32); + } + // Test that the shift count is coerced to an int32. + testBinaryScalarFunc(v, undefined, Uint32x4.shiftLeftByScalar, ulsh32); + testBinaryScalarFunc(v, 3.5, Uint32x4.shiftLeftByScalar, ulsh32); + testBinaryScalarFunc(v, good, Uint32x4.shiftLeftByScalar, ulsh32); + } + + var v = SIMD.Int8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); + assertThrowsInstanceOf(() => SIMD.Int8x16.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Int8x16.shiftRightByScalar(v, bad), TestError); + + var v = SIMD.Int16x8(1,2,3,4,5,6,7,8); + assertThrowsInstanceOf(() => SIMD.Int16x8.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Int16x8.shiftRightByScalar(v, bad), TestError); + + var v = SIMD.Int32x4(1,2,3,4); + assertThrowsInstanceOf(() => SIMD.Int32x4.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Int32x4.shiftRightByScalar(v, bad), TestError); + + var v = SIMD.Uint8x16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16); + assertThrowsInstanceOf(() => SIMD.Uint8x16.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.shiftRightByScalar(v, bad), TestError); + + var v = SIMD.Uint16x8(1,2,3,4,5,6,7,8); + assertThrowsInstanceOf(() => SIMD.Uint16x8.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.shiftRightByScalar(v, bad), TestError); + + var v = SIMD.Uint32x4(1,2,3,4); + assertThrowsInstanceOf(() => SIMD.Uint32x4.shiftLeftByScalar(v, bad), TestError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.shiftRightByScalar(v, bad), TestError); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/splat.js b/js/src/tests/non262/SIMD/splat.js new file mode 100644 index 000000000000..1ab7ec98c6f4 --- /dev/null +++ b/js/src/tests/non262/SIMD/splat.js @@ -0,0 +1,97 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +var Float64x2 = SIMD.Float64x2; +var Float32x4 = SIMD.Float32x4; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; + +function TestSplatX16(type, inputs, coerceFunc) { + for (var x of inputs) { + assertEqX16(SIMD[type].splat(x), [x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x].map(coerceFunc)); + } +} + +function TestSplatX8(type, inputs, coerceFunc) { + for (var x of inputs) { + assertEqX8(SIMD[type].splat(x), [x, x, x, x, x, x, x, x].map(coerceFunc)); + } +} + +function TestSplatX4(type, inputs, coerceFunc) { + for (var x of inputs) { + assertEqX4(SIMD[type].splat(x), [x, x, x, x].map(coerceFunc)); + } +} + +function TestSplatX2(type, inputs, coerceFunc) { + for (var x of inputs) { + assertEqX2(SIMD[type].splat(x), [x, x].map(coerceFunc)); + } +} + +function test() { + function TestError(){}; + + var good = {valueOf: () => 19.89}; + var bad = {valueOf: () => { throw new TestError(); }}; + + TestSplatX16('Int8x16', [0, 1, 2, -1, -2, 3, -3, 4, -4, 5, -5, 6, INT8_MIN, INT8_MAX, INT8_MIN - 1, INT8_MAX + 1], (x) => x << 24 >> 24); + assertEqX16(Int8x16.splat(), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Int8x16.splat(bad), TestError); + + TestSplatX8('Int16x8', [0, 1, 2, -1, INT16_MIN, INT16_MAX, INT16_MIN - 1, INT16_MAX + 1], (x) => x << 16 >> 16); + assertEqX8(Int16x8.splat(), [0, 0, 0, 0, 0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Int16x8.splat(bad), TestError); + + TestSplatX4('Int32x4', [0, undefined, 3.5, 42, -1337, INT32_MAX, INT32_MAX + 1, good], (x) => x | 0); + assertEqX4(SIMD.Int32x4.splat(), [0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Int32x4.splat(bad), TestError); + + TestSplatX16('Uint8x16', [0, 1, 2, -1, -2, 3, -3, 4, -4, 5, -5, 6, INT8_MIN, INT8_MAX, INT8_MIN - 1, INT8_MAX + 1], (x) => x << 24 >>> 24); + assertEqX16(Uint8x16.splat(), [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Uint8x16.splat(bad), TestError); + + TestSplatX8('Uint16x8', [0, 1, 2, -1, INT16_MIN, INT16_MAX, INT16_MIN - 1, INT16_MAX + 1], (x) => x << 16 >>> 16); + assertEqX8(Uint16x8.splat(), [0, 0, 0, 0, 0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Uint16x8.splat(bad), TestError); + + TestSplatX4('Uint32x4', [0, undefined, 3.5, 42, INT32_MAX, INT32_MIN, UINT32_MAX, UINT32_MAX + 1, good], (x) => x >>> 0); + assertEqX4(SIMD.Uint32x4.splat(), [0, 0, 0, 0]); + assertThrowsInstanceOf(() => SIMD.Uint32x4.splat(bad), TestError); + + TestSplatX4('Float32x4', [0, undefined, 3.5, 42, -13.37, Infinity, NaN, -0, good], (x) => Math.fround(x)); + assertEqX4(SIMD.Float32x4.splat(), [NaN, NaN, NaN, NaN]); + assertThrowsInstanceOf(() => SIMD.Float32x4.splat(bad), TestError); + + TestSplatX2('Float64x2', [0, undefined, 3.5, 42, -13.37, Infinity, NaN, -0, good], (x) => +x); + assertEqX2(SIMD.Float64x2.splat(), [NaN, NaN]); + assertThrowsInstanceOf(() => SIMD.Float64x2.splat(bad), TestError); + + TestSplatX16('Bool8x16', [true, false], (x) => !!x); + assertEqX16(Bool8x16.splat(), [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]); + assertEqX16(Bool8x16.splat(bad), [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]); + + TestSplatX8('Bool16x8', [true, false], (x) => !!x); + assertEqX8(Bool16x8.splat(), [false, false, false, false, false, false, false, false]); + assertEqX8(Bool16x8.splat(bad), [true, true, true, true, true, true, true, true]); + + TestSplatX4('Bool32x4', [true, false], (x) => !!x); + assertEqX4(SIMD.Bool32x4.splat(), [false, false, false, false]); + assertEqX4(SIMD.Bool32x4.splat(bad), [true, true, true, true]); + + TestSplatX2('Bool64x2', [true, false], (x) => !!x); + assertEqX2(SIMD.Bool64x2.splat(), [false, false]); + assertEqX2(SIMD.Bool64x2.splat(bad), [true, true]); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/store.js b/js/src/tests/non262/SIMD/store.js new file mode 100644 index 000000000000..ef3c2249ea36 --- /dev/null +++ b/js/src/tests/non262/SIMD/store.js @@ -0,0 +1,264 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +// As SIMD.*.store is entirely symmetric to SIMD.*.load, this file just +// contains basic tests to store on one single TypedArray kind, while load is +// exhaustively tested. See load.js for more details. + +const POISON = 42; + +function reset(ta) { + for (var i = 0; i < ta.length; i++) + ta[i] = POISON + i; +} + +function assertChanged(ta, from, expected) { + var i = 0; + for (; i < from; i++) + assertEq(ta[i], POISON + i); + for (; i < from + expected.length; i++) + assertEq(ta[i], expected[i - from]); + for (; i < ta.length; i++) + assertEq(ta[i], POISON + i); +} + +function testStore(ta, kind, i, v) { + var asArr = simdToArray(v); + + reset(ta); + SIMD[kind].store(ta, i, v); + assertChanged(ta, i, asArr); + + var length = asArr.length; + if (length >= 8) // Int8x16 and Int16x8 only support store, and not store1/store2/etc. + return; + + reset(ta); + SIMD[kind].store1(ta, i, v); + assertChanged(ta, i, [asArr[0]]); + if (length > 2) { + reset(ta); + SIMD[kind].store2(ta, i, v); + assertChanged(ta, i, [asArr[0], asArr[1]]); + + reset(ta); + SIMD[kind].store3(ta, i, v); + assertChanged(ta, i, [asArr[0], asArr[1], asArr[2]]); + } +} + +function testStoreInt8x16(Buffer) { + var I8 = new Int8Array(new Buffer(32)); + + var v = SIMD.Int8x16(0, 1, INT8_MAX, INT8_MIN, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + testStore(I8, 'Int8x16', 0, v); + testStore(I8, 'Int8x16', 1, v); + testStore(I8, 'Int8x16', 2, v); + testStore(I8, 'Int8x16', 16, v); + + assertThrowsInstanceOf(() => SIMD.Int8x16.store(I8), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.store(I8, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.store(I8, 0, v), TypeError); +} + +function testStoreInt16x8(Buffer) { + var I16 = new Int16Array(new Buffer(64)); + + var v = SIMD.Int16x8(0, 1, INT16_MAX, INT16_MIN, 4, 5, 6, 7); + testStore(I16, 'Int16x8', 0, v); + testStore(I16, 'Int16x8', 1, v); + testStore(I16, 'Int16x8', 2, v); + testStore(I16, 'Int16x8', 24, v); + + assertThrowsInstanceOf(() => SIMD.Int16x8.store(I16), TypeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.store(I16, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Int8x16.store(I16, 0, v), TypeError); +} + +function testStoreInt32x4(Buffer) { + var I32 = new Int32Array(new Buffer(64)); + + var v = SIMD.Int32x4(0, 1, Math.pow(2,31) - 1, -Math.pow(2, 31)); + testStore(I32, 'Int32x4', 0, v); + testStore(I32, 'Int32x4', 1, v); + testStore(I32, 'Int32x4', 2, v); + testStore(I32, 'Int32x4', 12, v); + + assertThrowsInstanceOf(() => SIMD.Int32x4.store(I32), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.store(I32, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.store(I32, 0, v), TypeError); +} + +function testStoreUint8x16(Buffer) { + var I8 = new Uint8Array(new Buffer(32)); + + var v = SIMD.Uint8x16(0, 1, INT8_MAX, INT8_MIN, UINT8_MAX, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + testStore(I8, 'Uint8x16', 0, v); + testStore(I8, 'Uint8x16', 1, v); + testStore(I8, 'Uint8x16', 2, v); + testStore(I8, 'Uint8x16', 16, v); + + assertThrowsInstanceOf(() => SIMD.Uint8x16.store(I8), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.store(I8, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.store(I8, 0, v), TypeError); +} + +function testStoreUint16x8(Buffer) { + var I16 = new Uint16Array(new Buffer(64)); + + var v = SIMD.Uint16x8(0, 1, INT16_MAX, INT16_MIN, 4, 5, 6, 7); + testStore(I16, 'Uint16x8', 0, v); + testStore(I16, 'Uint16x8', 1, v); + testStore(I16, 'Uint16x8', 2, v); + testStore(I16, 'Uint16x8', 24, v); + + assertThrowsInstanceOf(() => SIMD.Uint16x8.store(I16), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint16x8.store(I16, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint8x16.store(I16, 0, v), TypeError); +} + +function testStoreUint32x4(Buffer) { + var I32 = new Uint32Array(new Buffer(64)); + + var v = SIMD.Uint32x4(0, 1, Math.pow(2,31) - 1, -Math.pow(2, 31)); + testStore(I32, 'Uint32x4', 0, v); + testStore(I32, 'Uint32x4', 1, v); + testStore(I32, 'Uint32x4', 2, v); + testStore(I32, 'Uint32x4', 12, v); + + assertThrowsInstanceOf(() => SIMD.Uint32x4.store(I32), TypeError); + assertThrowsInstanceOf(() => SIMD.Uint32x4.store(I32, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.store(I32, 0, v), TypeError); +} + +function testStoreFloat32x4(Buffer) { + var F32 = new Float32Array(new Buffer(64)); + + var v = SIMD.Float32x4(1,2,3,4); + testStore(F32, 'Float32x4', 0, v); + testStore(F32, 'Float32x4', 1, v); + testStore(F32, 'Float32x4', 2, v); + testStore(F32, 'Float32x4', 12, v); + + var v = SIMD.Float32x4(NaN, -0, -Infinity, 5e-324); + testStore(F32, 'Float32x4', 0, v); + testStore(F32, 'Float32x4', 1, v); + testStore(F32, 'Float32x4', 2, v); + testStore(F32, 'Float32x4', 12, v); + + assertThrowsInstanceOf(() => SIMD.Float32x4.store(F32), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.store(F32, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.store(F32, 0, v), TypeError); +} + +function testStoreFloat64x2(Buffer) { + var F64 = new Float64Array(new Buffer(128)); + + var v = SIMD.Float64x2(1, 2); + testStore(F64, 'Float64x2', 0, v); + testStore(F64, 'Float64x2', 1, v); + testStore(F64, 'Float64x2', 14, v); + + var v = SIMD.Float64x2(NaN, -0); + testStore(F64, 'Float64x2', 0, v); + testStore(F64, 'Float64x2', 1, v); + testStore(F64, 'Float64x2', 14, v); + + var v = SIMD.Float64x2(-Infinity, +Infinity); + testStore(F64, 'Float64x2', 0, v); + testStore(F64, 'Float64x2', 1, v); + testStore(F64, 'Float64x2', 14, v); + + assertThrowsInstanceOf(() => SIMD.Float64x2.store(F64), TypeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.store(F64, 0), TypeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.store(F64, 0, v), TypeError); +} + +function testSharedArrayBufferCompat() { + var I32 = new Int32Array(new SharedArrayBuffer(16*4)); + var TA = I32; + + var I8 = new Int8Array(TA.buffer); + var I16 = new Int16Array(TA.buffer); + var F32 = new Float32Array(TA.buffer); + var F64 = new Float64Array(TA.buffer); + + var Int8x16 = SIMD.Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var Int16x8 = SIMD.Int16x8(1, 2, 3, 4, 5, 6, 7, 8); + var Int32x4 = SIMD.Int32x4(1, 2, 3, 4); + var Uint8x16 = SIMD.Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var Uint16x8 = SIMD.Uint16x8(1, 2, 3, 4, 5, 6, 7, 8); + var Uint32x4 = SIMD.Uint32x4(1, 2, 3, 4); + var Float32x4 = SIMD.Float32x4(1, 2, 3, 4); + var Float64x2 = SIMD.Float64x2(1, 2); + + for (var ta of [ + new Uint8Array(TA.buffer), + new Int8Array(TA.buffer), + new Uint16Array(TA.buffer), + new Int16Array(TA.buffer), + new Uint32Array(TA.buffer), + new Int32Array(TA.buffer), + new Float32Array(TA.buffer), + new Float64Array(TA.buffer) + ]) + { + SIMD.Int8x16.store(ta, 0, Int8x16); + for (var i = 0; i < 16; i++) assertEq(I8[i], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16][i]); + + SIMD.Int16x8.store(ta, 0, Int16x8); + for (var i = 0; i < 8; i++) assertEq(I16[i], [1, 2, 3, 4, 5, 6, 7, 8][i]); + + SIMD.Int32x4.store(ta, 0, Int32x4); + for (var i = 0; i < 4; i++) assertEq(I32[i], [1, 2, 3, 4][i]); + + SIMD.Uint8x16.store(ta, 0, Uint8x16); + for (var i = 0; i < 16; i++) assertEq(I8[i], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16][i]); + + SIMD.Uint16x8.store(ta, 0, Uint16x8); + for (var i = 0; i < 8; i++) assertEq(I16[i], [1, 2, 3, 4, 5, 6, 7, 8][i]); + + SIMD.Uint32x4.store(ta, 0, Uint32x4); + for (var i = 0; i < 4; i++) assertEq(I32[i], [1, 2, 3, 4][i]); + + SIMD.Float32x4.store(ta, 0, Float32x4); + for (var i = 0; i < 4; i++) assertEq(F32[i], [1, 2, 3, 4][i]); + + SIMD.Float64x2.store(ta, 0, Float64x2); + for (var i = 0; i < 2; i++) assertEq(F64[i], [1, 2][i]); + + assertThrowsInstanceOf(() => SIMD.Int8x16.store(ta, 1024, Int8x16), RangeError); + assertThrowsInstanceOf(() => SIMD.Int16x8.store(ta, 1024, Int16x8), RangeError); + assertThrowsInstanceOf(() => SIMD.Int32x4.store(ta, 1024, Int32x4), RangeError); + assertThrowsInstanceOf(() => SIMD.Float32x4.store(ta, 1024, Float32x4), RangeError); + assertThrowsInstanceOf(() => SIMD.Float64x2.store(ta, 1024, Float64x2), RangeError); + } +} + +testStoreInt8x16(ArrayBuffer); +testStoreInt16x8(ArrayBuffer); +testStoreInt32x4(ArrayBuffer); +testStoreUint8x16(ArrayBuffer); +testStoreUint16x8(ArrayBuffer); +testStoreUint32x4(ArrayBuffer); +testStoreFloat32x4(ArrayBuffer); +testStoreFloat64x2(ArrayBuffer); + +if (typeof SharedArrayBuffer != "undefined") { + testStoreInt8x16(SharedArrayBuffer); + testStoreInt16x8(SharedArrayBuffer); + testStoreInt32x4(SharedArrayBuffer); + testStoreUint8x16(SharedArrayBuffer); + testStoreUint16x8(SharedArrayBuffer); + testStoreUint32x4(SharedArrayBuffer); + testStoreFloat32x4(SharedArrayBuffer); + testStoreFloat64x2(SharedArrayBuffer); + testSharedArrayBufferCompat(); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/swizzle-shuffle.js b/js/src/tests/non262/SIMD/swizzle-shuffle.js new file mode 100644 index 000000000000..7af98e2a76db --- /dev/null +++ b/js/src/tests/non262/SIMD/swizzle-shuffle.js @@ -0,0 +1,507 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +/* + * Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ + */ + +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; + +function swizzle2(arr, x, y) { + return [arr[x], arr[y]]; +} + +function swizzle4(arr, x, y, z, w) { + return [arr[x], arr[y], arr[z], arr[w]]; +} + +function swizzle8(arr, s0, s1, s2, s3, s4, s5, s6, s7) { + return [arr[s0], arr[s1], arr[s2], arr[s3], arr[s4], arr[s5], arr[s6], arr[s7]]; +} + +function swizzle16(arr, s0, s1, s2, s3, s4, s5, s6, s7, + s8, s9, s10, s11, s12, s13, s14, s15) { + return [arr[s0], arr[s1], arr[s2], arr[s3], arr[s4], arr[s5], arr[s6], arr[s7], + arr[s8], arr[s9], arr[s10], arr[s11], arr[s12], arr[s13], arr[s14], arr[s15]]; +} + +function getNumberOfLanesFromType(type) { + switch (type) { + case Int8x16: + case Uint8x16: + return 16; + case Int16x8: + case Uint16x8: + return 8; + case Float32x4: + case Int32x4: + case Uint32x4: + return 4; + case Float64x2: + return 2; + } + throw new TypeError("Unknown SIMD type."); +} + +function testSwizzleForType(type) { + var lanes = getNumberOfLanesFromType(type); + var v; + switch (lanes) { + case 2: + v = type(1, 2); + break; + case 4: + v = type(1, 2, 3, 4); + break; + case 8: + v = type(1, 2, 3, 4, 5, 6, 7, 8); + break; + case 16: + v = type(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + break; + } + + assertThrowsInstanceOf(() => type.swizzle() , TypeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0) , TypeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 1, 2) , TypeError); + assertThrowsInstanceOf(() => type.swizzle(0, 1, 2, 3, v) , TypeError); + + // Test all possible swizzles. + if (lanes == 2) { + var x, y; + for (var i = 0; i < Math.pow(2, 2); i++) { + [x, y] = [x & 1, (y >> 1) & 1]; + assertEqVec(type.swizzle(v, x, y), swizzle2(simdToArray(v), x, y)); + } + } else if (lanes == 4) { + var x, y, z, w; + for (var i = 0; i < Math.pow(4, 4); i++) { + [x, y, z, w] = [i & 3, (i >> 2) & 3, (i >> 4) & 3, (i >> 6) & 3]; + assertEqVec(type.swizzle(v, x, y, z, w), swizzle4(simdToArray(v), x, y, z, w)); + } + } else if (lanes == 8) { + var vals = [[1, 2, 1, 2, 1, 2, 1, 2], [1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 2, 3, 4, 5, 6, 7], + [7, 6, 5, 4, 3, 2, 1, 0], [5, 3, 2, 6, 1, 7, 4, 0]]; + for (var t of vals) { + assertEqVec(type.swizzle(v, ...t), swizzle8(simdToArray(v), ...t)); + } + } else { + assertEq(lanes, 16); + + var vals = [[11, 2, 11, 2, 11, 2, 11, 2, 11, 2, 11, 2, 11, 2, 11, 2], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + [5, 14, 3, 2, 6, 9, 1, 10, 7, 11, 4, 0, 13, 15, 8, 12]]; + for (var t of vals) { + assertEqVec(type.swizzle(v, ...t), swizzle16(simdToArray(v), ...t)); + } + } + + // Test that we throw if an lane argument doesn't coerce to an integer in bounds. + if (lanes == 2) { + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, undefined), RangeError); + + type.swizzle(v, 0, "00.0"); + type.swizzle(v, 0, null); + type.swizzle(v, 0, false); + type.swizzle(v, 0, true); + + // In bounds is [0, 1] + assertThrowsInstanceOf(() => type.swizzle(v, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 2), RangeError); + } else if (lanes == 4) { + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, undefined), RangeError); + + type.swizzle(v, 0, 0, 0, "00.0"); + type.swizzle(v, 0, 0, 0, null); + type.swizzle(v, 0, 0, 0, false); + type.swizzle(v, 0, 0, 0, true); + + // In bounds is [0, 3] + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 4), RangeError); + } else if (lanes == 8) { + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, undefined), RangeError); + + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, "00.0"); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, null); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, false); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, true); + + // In bounds is [0, 7] + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 8), RangeError); + } else { + assertEq(lanes, 16); + + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, undefined), RangeError); + + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "00.0"); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); + type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, true); + + // In bounds is [0, 15] + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16), RangeError); + } +} + +function testSwizzleInt8x16() { + var v = Int16x8(1, 2, 3, 4, 5, 6, 7, 8); + + assertThrowsInstanceOf(function() { + Int8x16.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Int8x16); +} + +function testSwizzleInt16x8() { + var v = Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + + assertThrowsInstanceOf(function() { + Int16x8.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Int16x8); +} + +function testSwizzleInt32x4() { + var v = Int32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Float32x4.swizzle(v, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Int32x4); +} + +function testSwizzleUint8x16() { + var v = Uint16x8(1, 2, 3, 4, 5, 6, 7, 8); + + assertThrowsInstanceOf(function() { + Uint8x16.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Uint8x16); +} + +function testSwizzleUint16x8() { + var v = Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + + assertThrowsInstanceOf(function() { + Uint16x8.swizzle(v, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Uint16x8); +} + +function testSwizzleUint32x4() { + var v = Uint32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Float32x4.swizzle(v, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Uint32x4); +} + +function testSwizzleFloat32x4() { + var v = Float32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Int32x4.swizzle(v, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Float32x4); +} + +function testSwizzleFloat64x2() { + var v = Float64x2(1, 2); + + assertThrowsInstanceOf(function() { + Float32x4.swizzle(v, 0, 0, 0, 0); + }, TypeError); + + testSwizzleForType(Float64x2); +} + +function shuffle2(lhsa, rhsa, x, y) { + return [(x < 2 ? lhsa : rhsa)[x % 2], + (y < 2 ? lhsa : rhsa)[y % 2]]; +} +function shuffle4(lhsa, rhsa, x, y, z, w) { + return [(x < 4 ? lhsa : rhsa)[x % 4], + (y < 4 ? lhsa : rhsa)[y % 4], + (z < 4 ? lhsa : rhsa)[z % 4], + (w < 4 ? lhsa : rhsa)[w % 4]]; +} + +function shuffle8(lhsa, rhsa, s0, s1, s2, s3, s4, s5, s6, s7, s8) { + return [(s0 < 8 ? lhsa : rhsa)[s0 % 8], + (s1 < 8 ? lhsa : rhsa)[s1 % 8], + (s2 < 8 ? lhsa : rhsa)[s2 % 8], + (s3 < 8 ? lhsa : rhsa)[s3 % 8], + (s4 < 8 ? lhsa : rhsa)[s4 % 8], + (s5 < 8 ? lhsa : rhsa)[s5 % 8], + (s6 < 8 ? lhsa : rhsa)[s6 % 8], + (s7 < 8 ? lhsa : rhsa)[s7 % 8]]; +} + +function shuffle16(lhsa, rhsa, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15) { + return [(s0 < 16 ? lhsa : rhsa)[s0 % 16], + (s1 < 16 ? lhsa : rhsa)[s1 % 16], + (s2 < 16 ? lhsa : rhsa)[s2 % 16], + (s3 < 16 ? lhsa : rhsa)[s3 % 16], + (s4 < 16 ? lhsa : rhsa)[s4 % 16], + (s5 < 16 ? lhsa : rhsa)[s5 % 16], + (s6 < 16 ? lhsa : rhsa)[s6 % 16], + (s7 < 16 ? lhsa : rhsa)[s7 % 16], + (s8 < 16 ? lhsa : rhsa)[s8 % 16], + (s9 < 16 ? lhsa : rhsa)[s9 % 16], + (s10 < 16 ? lhsa : rhsa)[s10 % 16], + (s11 < 16 ? lhsa : rhsa)[s11 % 16], + (s12 < 16 ? lhsa : rhsa)[s12 % 16], + (s13 < 16 ? lhsa : rhsa)[s13 % 16], + (s14 < 16 ? lhsa : rhsa)[s14 % 16], + (s15 < 16 ? lhsa : rhsa)[s15 % 16]]; +} + +function testShuffleForType(type) { + var lanes = getNumberOfLanesFromType(type); + var lhs, rhs; + if (lanes == 2) { + lhs = type(1, 2); + rhs = type(3, 4); + } else if (lanes == 4) { + lhs = type(1, 2, 3, 4); + rhs = type(5, 6, 7, 8); + } else if (lanes == 8) { + lhs = type(1, 2, 3, 4, 5, 6, 7, 8); + rhs = type(9, 10, 11, 12, 13, 14, 15, 16); + } else { + assertEq(lanes, 16); + + lhs = type(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + rhs = type(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32); + } + + assertThrowsInstanceOf(() => type.shuffle(lhs) , TypeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs) , TypeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0) , TypeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 1, 2) , TypeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, 0, 1, 2, 7, rhs) , TypeError); + + // Test all possible shuffles. + var x, y, z, w; + if (lanes == 2) { + var x, y; + for (var i = 0; i < Math.pow(4, 2); i++) { + [x, y] = [i & 3, (i >> 3) & 3]; + assertEqVec(type.shuffle(lhs, rhs, x, y), + shuffle2(simdToArray(lhs), simdToArray(rhs), x, y)); + } + } else if (lanes == 4) { + var x, y, z, w; + for (var i = 0; i < Math.pow(8, 4); i++) { + [x, y, z, w] = [i & 7, (i >> 3) & 7, (i >> 6) & 7, (i >> 9) & 7]; + assertEqVec(type.shuffle(lhs, rhs, x, y, z, w), + shuffle4(simdToArray(lhs), simdToArray(rhs), x, y, z, w)); + } + } else if (lanes == 8) { + var s0, s1, s2, s3, s4, s5, s6, s7; + var vals = [[15, 8, 15, 8, 15, 8, 15, 8], [9, 7, 9, 7, 9, 7, 9, 7], + [7, 3, 8, 9, 2, 15, 14, 6], [2, 2, 2, 2, 2, 2, 2, 2], + [8, 8, 8, 8, 8, 8, 8, 8], [11, 11, 11, 11, 11, 11, 11, 11]]; + for (var t of vals) { + [s0, s1, s2, s3, s4, s5, s6, s7] = t; + assertEqVec(type.shuffle(lhs, rhs, s0, s1, s2, s3, s4, s5, s6, s7), + shuffle8(simdToArray(lhs), simdToArray(rhs), s0, s1, s2, s3, s4, s5, s6, s7)); + } + } else { + assertEq(lanes, 16); + + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15; + var vals = [[30, 16, 30, 16, 30, 16, 30, 16, 30, 16, 30, 16, 30, 16, 30, 16], + [19, 17, 19, 17, 19, 17, 19, 17, 19, 17, 19, 17, 19, 17, 19, 17], + [7, 3, 8, 18, 9, 21, 2, 15, 14, 6, 16, 22, 29, 31, 30, 1], + [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], + [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + [21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21]]; + for (var t of vals) { + [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15] = t; + assertEqVec(type.shuffle(lhs, rhs, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15), + shuffle16(simdToArray(lhs), simdToArray(rhs), s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15)); + } + } + + // Test that we throw if an lane argument isn't an int32 or isn't in bounds. + if (lanes == 2) { + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, undefined), RangeError); + + // In bounds is [0, 3] + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 4), RangeError); + } else if (lanes == 4) { + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, undefined), RangeError); + + // In bounds is [0, 7] + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 8), RangeError); + } else if (lanes == 8) { + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, undefined), RangeError); + + // In bounds is [0, 15] + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 16), RangeError); + } else { + assertEq(lanes, 16); + + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {valueOf: function(){return 42}}), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "one"), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, undefined), RangeError); + + // In bounds is [0, 31] + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1), RangeError); + assertThrowsInstanceOf(() => type.shuffle(lhs, rhs, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32), RangeError); + } +} + +function testShuffleInt8x16() { + var v = Int16x8(1, 2, 3, 4, 5, 6, 7, 8); + + assertThrowsInstanceOf(function() { + Int8x16.shuffle(v, v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Int8x16); +} + +function testShuffleInt16x8() { + var v = Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + + assertThrowsInstanceOf(function() { + Int16x8.shuffle(v, v, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Int16x8); +} + +function testShuffleInt32x4() { + var v = Int32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Float32x4.shuffle(v, v, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Int32x4); +} + +function testShuffleUint8x16() { + var v = Uint16x8(1, 2, 3, 4, 5, 6, 7, 8); + + assertThrowsInstanceOf(function() { + Uint8x16.shuffle(v, v, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Uint8x16); +} + +function testShuffleUint16x8() { + var v = Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + + assertThrowsInstanceOf(function() { + Uint16x8.shuffle(v, v, 0, 0, 0, 0, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Uint16x8); +} + +function testShuffleUint32x4() { + var v = Uint32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Float32x4.shuffle(v, v, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Uint32x4); +} + +function testShuffleFloat32x4() { + var v = Float32x4(1, 2, 3, 4); + + assertThrowsInstanceOf(function() { + Int32x4.shuffle(v, v, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Float32x4); +} + +function testShuffleFloat64x2() { + var v = Float64x2(1, 2); + + assertThrowsInstanceOf(function() { + Float32x4.shuffle(v, v, 0, 0, 0, 0); + }, TypeError); + + testShuffleForType(Float64x2); +} + +testSwizzleInt8x16(); +testSwizzleInt16x8(); +testSwizzleInt32x4(); +testSwizzleUint8x16(); +testSwizzleUint16x8(); +testSwizzleUint32x4(); +testSwizzleFloat32x4(); +testSwizzleFloat64x2(); +testShuffleInt8x16(); +testShuffleInt16x8(); +testShuffleInt32x4(); +testShuffleUint8x16(); +testShuffleUint16x8(); +testShuffleUint32x4(); +testShuffleFloat32x4(); +testShuffleFloat64x2(); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/non262/SIMD/toString.js b/js/src/tests/non262/SIMD/toString.js new file mode 100644 index 000000000000..0f8bb7a9b280 --- /dev/null +++ b/js/src/tests/non262/SIMD/toString.js @@ -0,0 +1,90 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) + +function test() { + var Float32x4 = SIMD.Float32x4; + var f = Float32x4(11, 22, NaN, -44); + assertEq(f.toString(), "SIMD.Float32x4(11, 22, NaN, -44)"); + + // Polyfill check should show that we already have a toString. + assertEq(Float32x4.prototype.hasOwnProperty("toString"), true); + + // This toString method type checks its argument. + var ts = Float32x4.prototype.toString; + assertThrowsInstanceOf(() => ts.call(5), TypeError); + assertThrowsInstanceOf(() => ts.call({}), TypeError); + + // Can't convert SIMD objects to numbers. + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Float64x2 = SIMD.Float64x2; + var f = Float64x2(11, 22); + assertEq(f.toString(), "SIMD.Float64x2(11, 22)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Int8x16 = SIMD.Int8x16; + var f = Int8x16(11, 22, 33, 44, -11, -22, -33, -44, 1, 2, 3, 4, -1, -2, -3, -4); + assertEq(f.toString(), "SIMD.Int8x16(11, 22, 33, 44, -11, -22, -33, -44, 1, 2, 3, 4, -1, -2, -3, -4)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Int16x8 = SIMD.Int16x8; + var f = Int16x8(11, 22, 33, 44, -11, -22, -33, -44); + assertEq(f.toString(), "SIMD.Int16x8(11, 22, 33, 44, -11, -22, -33, -44)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Int32x4 = SIMD.Int32x4; + var f = Int32x4(11, 22, 33, 44); + assertEq(f.toString(), "SIMD.Int32x4(11, 22, 33, 44)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Uint8x16 = SIMD.Uint8x16; + var f = Uint8x16(11, 22, 33, 44, 245, 234, 223, 212, 1, 2, 3, 4, 255, 254, 0, 250); + assertEq(f.toString(), "SIMD.Uint8x16(11, 22, 33, 44, 245, 234, 223, 212, 1, 2, 3, 4, 255, 254, 0, 250)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Uint16x8 = SIMD.Uint16x8; + var f = Uint16x8(11, 22, 33, 44, 65535, 65534, 65533, 65532); + assertEq(f.toString(), "SIMD.Uint16x8(11, 22, 33, 44, 65535, 65534, 65533, 65532)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Uint32x4 = SIMD.Uint32x4; + var f = Uint32x4(11, 22, 4294967295, 4294967294); + assertEq(f.toString(), "SIMD.Uint32x4(11, 22, 4294967295, 4294967294)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Bool8x16 = SIMD.Bool8x16; + var f = Bool8x16(true, true, false, false, false, true, true, false, true, true, true, true, false, false, false, false); + assertEq(f.toString(), "SIMD.Bool8x16(true, true, false, false, false, true, true, false, true, true, true, true, false, false, false, false)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Bool16x8 = SIMD.Bool16x8; + var f = Bool16x8(true, true, false, false, true, false, false, true); + assertEq(f.toString(), "SIMD.Bool16x8(true, true, false, false, true, false, false, true)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Bool32x4 = SIMD.Bool32x4; + var f = Bool32x4(true, true, false, false); + assertEq(f.toString(), "SIMD.Bool32x4(true, true, false, false)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + var Bool64x2 = SIMD.Bool64x2; + var f = Bool64x2(true, false); + assertEq(f.toString(), "SIMD.Bool64x2(true, false)"); + assertThrowsInstanceOf(() => +f, TypeError); + assertThrowsInstanceOf(() => f.valueOf(), TypeError); + + if (typeof reportCompare === "function") + reportCompare(true, true); +} + +test(); diff --git a/js/src/tests/non262/SIMD/typedobjects.js b/js/src/tests/non262/SIMD/typedobjects.js new file mode 100644 index 000000000000..e4886cf4ea2d --- /dev/null +++ b/js/src/tests/non262/SIMD/typedobjects.js @@ -0,0 +1,1077 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float32x4 = SIMD.Float32x4; +var Float64x2 = SIMD.Float64x2; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +var {StructType, Handle} = TypedObject; +var {float32, float64, int8, int16, int32, uint8} = TypedObject; + +function testFloat32x4Alignment() { + assertEq(Float32x4.byteLength, 16); + assertEq(Float32x4.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Float32x4}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testFloat32x4Getters() { + var f = Float32x4(11, 22, 33, 44); + assertEq(Float32x4.extractLane(f, 0), 11); + assertEq(Float32x4.extractLane(f, 1), 22); + assertEq(Float32x4.extractLane(f, 2), 33); + assertEq(Float32x4.extractLane(f, 3), 44); + + assertThrowsInstanceOf(() => Float32x4.extractLane(f, 4), RangeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(Int32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Float32x4.extractLane(f, f), TypeError); +} + +function testFloat32x4Handles() { + var Array = Float32x4.array(3); + var array = new Array([Float32x4(1, 2, 3, 4), + Float32x4(5, 6, 7, 8), + Float32x4(9, 10, 11, 12)]); + + // Test that trying to create handle into the interior of a + // Float32x4 fails. + assertThrowsInstanceOf(function() { + var h = float32.handle(array, 1, 0); + }, TypeError, "Creating a float32 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = float32.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a float32 handle to elem via move"); +} + +function testFloat32x4Reify() { + var Array = Float32x4.array(3); + var array = new Array([Float32x4(1, 2, 3, 4), + Float32x4(5, 6, 7, 8), + Float32x4(9, 10, 11, 12)]); + + // Test that reading array[1] produces a *copy* of Float32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(Float32x4.extractLane(f, 3), 8); + assertEq(Float32x4.extractLane(array[1], 3), 8); + array[1] = Float32x4(15, 16, 17, 18); + assertEq(Float32x4.extractLane(f, 3), 8); + assertEq(Float32x4.extractLane(array[1], 3), 18); +} + +function testFloat32x4Setters() { + var Array = Float32x4.array(3); + var array = new Array([Float32x4(1, 2, 3, 4), + Float32x4(5, 6, 7, 8), + Float32x4(9, 10, 11, 12)]); + assertEq(Float32x4.extractLane(array[1], 3), 8); + + // Test that we are allowed to write Float32x4 values into array, + // but not other things. + + array[1] = Float32x4(15, 16, 17, 18); + assertEq(Float32x4.extractLane(array[1], 3), 18); + + assertThrowsInstanceOf(function() { + array[1] = {x: 15, y: 16, z: 17, w: 18}; + }, TypeError, "Setting Float32x4 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [15, 16, 17, 18]; + }, TypeError, "Setting Float32x4 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 22; + }, TypeError, "Setting Float32x4 from a number"); +} + +function testFloat64x2Alignment() { + assertEq(Float64x2.byteLength, 16); + assertEq(Float64x2.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Float64x2}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testFloat64x2Getters() { + // Create a Float64x2 and check that the getters work: + var f = Float64x2(11, 22); + assertEq(Float64x2.extractLane(f, 0), 11); + assertEq(Float64x2.extractLane(f, 1), 22); + + assertThrowsInstanceOf(() => Float64x2.extractLane(f, 2), RangeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(Float32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Float64x2.extractLane(f, f), TypeError); +} + +function testFloat64x2Handles() { + var Array = Float64x2.array(3); + var array = new Array([Float64x2(1, 2), + Float64x2(3, 4), + Float64x2(5, 6)]); + + // Test that trying to create handle into the interior of a + // Float64x2 fails. + assertThrowsInstanceOf(function() { + var h = float64.handle(array, 1, 0); + }, TypeError, "Creating a float64 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = float64.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a float64 handle to elem via move"); +} + +function testFloat64x2Reify() { + var Array = Float64x2.array(3); + var array = new Array([Float64x2(1, 2), + Float64x2(3, 4), + Float64x2(5, 6)]); + + // Test that reading array[1] produces a *copy* of Float64x2, not an + // alias into the array. + + var f = array[1]; + assertEq(Float64x2.extractLane(f, 1), 4); + assertEq(Float64x2.extractLane(array[1], 1), 4); + array[1] = Float64x2(7, 8); + assertEq(Float64x2.extractLane(f, 1), 4); + assertEq(Float64x2.extractLane(array[1], 1), 8); +} + +function testFloat64x2Setters() { + var Array = Float64x2.array(3); + var array = new Array([Float64x2(1, 2), + Float64x2(3, 4), + Float64x2(5, 6)]); + assertEq(Float64x2.extractLane(array[1], 1), 4); + + // Test that we are allowed to write Float64x2 values into array, + // but not other things. + + array[1] = Float64x2(7, 8); + assertEq(Float64x2.extractLane(array[1], 1), 8); + + assertThrowsInstanceOf(function() { + array[1] = {x: 7, y: 8 }; + }, TypeError, "Setting Float64x2 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [ 7, 8 ]; + }, TypeError, "Setting Float64x2 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 9; + }, TypeError, "Setting Float64x2 from a number"); +} + +function testInt8x16Alignment() { + assertEq(Int8x16.byteLength, 16); + assertEq(Int8x16.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Int8x16}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testInt8x16Getters() { + // Create a Int8x16 and check that the getters work: + var f = Int8x16(11, 22, 33, 44, 55, 66, 77, 88, 99, 10, 20, 30, 40, 50, 60, 70); + assertEq(Int8x16.extractLane(f, 0), 11); + assertEq(Int8x16.extractLane(f, 1), 22); + assertEq(Int8x16.extractLane(f, 2), 33); + assertEq(Int8x16.extractLane(f, 3), 44); + assertEq(Int8x16.extractLane(f, 4), 55); + assertEq(Int8x16.extractLane(f, 5), 66); + assertEq(Int8x16.extractLane(f, 6), 77); + assertEq(Int8x16.extractLane(f, 7), 88); + assertEq(Int8x16.extractLane(f, 8), 99); + assertEq(Int8x16.extractLane(f, 9), 10); + assertEq(Int8x16.extractLane(f, 10), 20); + assertEq(Int8x16.extractLane(f, 11), 30); + assertEq(Int8x16.extractLane(f, 12), 40); + assertEq(Int8x16.extractLane(f, 13), 50); + assertEq(Int8x16.extractLane(f, 14), 60); + assertEq(Int8x16.extractLane(f, 15), 70); + + assertThrowsInstanceOf(() => Int8x16.extractLane(f, 16), RangeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(Int32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Int8x16.extractLane(f, f), TypeError); +} + +function testInt8x16Handles() { + var Array = Int8x16.array(3); + var array = new Array([Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Int8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Int8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + + // Test that trying to create handle into the interior of a + // Int8x16 fails. + assertThrowsInstanceOf(function() { + var h = int8.handle(array, 1, 0); + }, TypeError, "Creating a int8 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int8.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int8 handle to elem via move"); +} + +function testInt8x16Reify() { + var Array = Int8x16.array(3); + var array = new Array([Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Int8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Int8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + + // Test that reading array[1] produces a *copy* of Int8x16, not an + // alias into the array. + + var f = array[1]; + + var sj1 = Int8x16.extractLane(f, 3); + + assertEq(sj1, 20); + assertEq(Int8x16.extractLane(array[1], 3), 20); + array[1] = Int8x16(49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64); + assertEq(Int8x16.extractLane(f, 3), 20); + assertEq(Int8x16.extractLane(array[1], 3), 52); +} + +function testInt8x16Setters() { + var Array = Int8x16.array(3); + var array = new Array([Int8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Int8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Int8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + assertEq(Int8x16.extractLane(array[1], 3), 20); + + // Test that we are allowed to write Int8x16 values into array, + // but not other things. + + array[1] = Int8x16(49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64); + assertEq(Int8x16.extractLane(array[1], 3), 52); + + assertThrowsInstanceOf(function() { + array[1] = {s0: 49, s1: 50, s2: 51, s3: 52, s4: 53, s5: 54, s6: 55, s7: 56, + s8: 57, s9: 58, s10: 59, s11: 60, s12: 61, s13: 62, s14: 63, s15: 64}; + }, TypeError, "Setting Int8x16 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]; + }, TypeError, "Setting Int8x16 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 52; + }, TypeError, "Setting Int8x16 from a number"); +} + +function testInt16x8Alignment() { + assertEq(Int16x8.byteLength, 16); + assertEq(Int16x8.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Int16x8}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testInt16x8Getters() { + // Create a Int16x8 and check that the getters work: + var f = Int16x8(11, 22, 33, 44, 55, 66, 77, 88); + assertEq(Int16x8.extractLane(f, 0), 11); + assertEq(Int16x8.extractLane(f, 1), 22); + assertEq(Int16x8.extractLane(f, 2), 33); + assertEq(Int16x8.extractLane(f, 3), 44); + assertEq(Int16x8.extractLane(f, 4), 55); + assertEq(Int16x8.extractLane(f, 5), 66); + assertEq(Int16x8.extractLane(f, 6), 77); + assertEq(Int16x8.extractLane(f, 7), 88); + + assertThrowsInstanceOf(() => Int16x8.extractLane(f, 8), RangeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(Int32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Int16x8.extractLane(f, f), TypeError); +} + +function testInt16x8Handles() { + var Array = Int16x8.array(3); + var array = new Array([Int16x8(1, 2, 3, 4, 5, 6, 7, 8), + Int16x8(9, 10, 11, 12, 13, 14, 15, 16), + Int16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + + // Test that trying to create handle into the interior of a + // Int16x8 fails. + assertThrowsInstanceOf(function() { + var h = int16.handle(array, 1, 0); + }, TypeError, "Creating a int16 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int16.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int16 handle to elem via move"); +} + +function testInt16x8Reify() { + var Array = Int16x8.array(3); + var array = new Array([Int16x8(1, 2, 3, 4, 5, 6, 7, 8), + Int16x8(9, 10, 11, 12, 13, 14, 15, 16), + Int16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + + // Test that reading array[1] produces a *copy* of Int16x8, not an + // alias into the array. + + var f = array[1]; + assertEq(Int16x8.extractLane(f, 3), 12); + assertEq(Int16x8.extractLane(array[1], 3), 12); + array[1] = Int16x8(25, 26, 27, 28, 29, 30, 31, 32); + assertEq(Int16x8.extractLane(f, 3), 12); + assertEq(Int16x8.extractLane(array[1], 3), 28); +} + +function testInt16x8Setters() { + var Array = Int16x8.array(3); + var array = new Array([Int16x8(1, 2, 3, 4, 5, 6, 7, 8), + Int16x8(9, 10, 11, 12, 13, 14, 15, 16), + Int16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + assertEq(Int16x8.extractLane(array[1], 3), 12); + + // Test that we are allowed to write Int16x8 values into array, + // but not other things. + + array[1] = Int16x8(25, 26, 27, 28, 29, 30, 31, 32); + assertEq(Int16x8.extractLane(array[1], 3), 28); + + assertThrowsInstanceOf(function() { + array[1] = {s0: 25, s1: 26, s2: 27, s3: 28, s4: 29, s5: 30, s6: 31, s7: 32}; + }, TypeError, "Setting Int16x8 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [25, 26, 27, 28, 29, 30, 31, 32]; + }, TypeError, "Setting Int16x8 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 28; + }, TypeError, "Setting Int16x8 from a number"); +} + +function testInt32x4Alignment() { + assertEq(Int32x4.byteLength, 16); + assertEq(Int32x4.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Int32x4}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testInt32x4Getters() { + // Create a Int32x4 and check that the getters work: + var f = Int32x4(11, 22, 33, 44); + assertEq(Int32x4.extractLane(f, 0), 11); + assertEq(Int32x4.extractLane(f, 1), 22); + assertEq(Int32x4.extractLane(f, 2), 33); + assertEq(Int32x4.extractLane(f, 3), 44); + + assertThrowsInstanceOf(() => Int32x4.extractLane(f, 4), RangeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(Float32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Int32x4.extractLane(f, f), TypeError); +} + +function testInt32x4Handles() { + var Array = Int32x4.array(3); + var array = new Array([Int32x4(1, 2, 3, 4), + Int32x4(5, 6, 7, 8), + Int32x4(9, 10, 11, 12)]); + + // Test that trying to create handle into the interior of a + // Int32x4 fails. + assertThrowsInstanceOf(function() { + var h = int32.handle(array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int32.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via move"); +} + +function testInt32x4Reify() { + var Array = Int32x4.array(3); + var array = new Array([Int32x4(1, 2, 3, 4), + Int32x4(5, 6, 7, 8), + Int32x4(9, 10, 11, 12)]); + + // Test that reading array[1] produces a *copy* of Int32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(Int32x4.extractLane(f, 3), 8); + assertEq(Int32x4.extractLane(array[1], 3), 8); + array[1] = Int32x4(15, 16, 17, 18); + assertEq(Int32x4.extractLane(f, 3), 8); + assertEq(Int32x4.extractLane(array[1], 3), 18); +} + +function testInt32x4Setters() { + var Array = Int32x4.array(3); + var array = new Array([Int32x4(1, 2, 3, 4), + Int32x4(5, 6, 7, 8), + Int32x4(9, 10, 11, 12)]); + assertEq(Int32x4.extractLane(array[1], 3), 8); + + // Test that we are allowed to write Int32x4 values into array, + // but not other things. + array[1] = Int32x4(15, 16, 17, 18); + assertEq(Int32x4.extractLane(array[1], 3), 18); + + assertThrowsInstanceOf(function() { + array[1] = {x: 15, y: 16, z: 17, w: 18}; + }, TypeError, "Setting Int32x4 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [15, 16, 17, 18]; + }, TypeError, "Setting Int32x4 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 22; + }, TypeError, "Setting Int32x4 from a number"); +} + +function testUint8x16Alignment() { + assertEq(Uint8x16.byteLength, 16); + assertEq(Uint8x16.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Uint8x16}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testUint8x16Getters() { + // Create a Uint8x16 and check that the getters work: + var f = Uint8x16(11, 22, 33, 44, 55, 66, 77, 88, 99, 10, 20, 30, 40, 50, 60, 70); + assertEq(Uint8x16.extractLane(f, 0), 11); + assertEq(Uint8x16.extractLane(f, 1), 22); + assertEq(Uint8x16.extractLane(f, 2), 33); + assertEq(Uint8x16.extractLane(f, 3), 44); + assertEq(Uint8x16.extractLane(f, 4), 55); + assertEq(Uint8x16.extractLane(f, 5), 66); + assertEq(Uint8x16.extractLane(f, 6), 77); + assertEq(Uint8x16.extractLane(f, 7), 88); + assertEq(Uint8x16.extractLane(f, 8), 99); + assertEq(Uint8x16.extractLane(f, 9), 10); + assertEq(Uint8x16.extractLane(f, 10), 20); + assertEq(Uint8x16.extractLane(f, 11), 30); + assertEq(Uint8x16.extractLane(f, 12), 40); + assertEq(Uint8x16.extractLane(f, 13), 50); + assertEq(Uint8x16.extractLane(f, 14), 60); + assertEq(Uint8x16.extractLane(f, 15), 70); + + assertThrowsInstanceOf(() => Uint8x16.extractLane(f, 16), RangeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(Uint32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Uint8x16.extractLane(f, f), TypeError); +} + +function testUint8x16Handles() { + var Array = Uint8x16.array(3); + var array = new Array([Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Uint8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Uint8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + + // Test that trying to create handle into the interior of a + // Uint8x16 fails. + assertThrowsInstanceOf(function() { + var h = int8.handle(array, 1, 0); + }, TypeError, "Creating a int8 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int8.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int8 handle to elem via move"); +} + +function testUint8x16Reify() { + var Array = Uint8x16.array(3); + var array = new Array([Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Uint8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Uint8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + + // Test that reading array[1] produces a *copy* of Uint8x16, not an + // alias into the array. + + var f = array[1]; + + var sj1 = Uint8x16.extractLane(f, 3); + + assertEq(sj1, 20); + assertEq(Uint8x16.extractLane(array[1], 3), 20); + array[1] = Uint8x16(49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64); + assertEq(Uint8x16.extractLane(f, 3), 20); + assertEq(Uint8x16.extractLane(array[1], 3), 52); +} + +function testUint8x16Setters() { + var Array = Uint8x16.array(3); + var array = new Array([Uint8x16(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + Uint8x16(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32), + Uint8x16(33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)]); + assertEq(Uint8x16.extractLane(array[1], 3), 20); + + // Test that we are allowed to write Uint8x16 values into array, + // but not other things. + + array[1] = Uint8x16(49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64); + assertEq(Uint8x16.extractLane(array[1], 3), 52); + + assertThrowsInstanceOf(function() { + array[1] = {s0: 49, s1: 50, s2: 51, s3: 52, s4: 53, s5: 54, s6: 55, s7: 56, + s8: 57, s9: 58, s10: 59, s11: 60, s12: 61, s13: 62, s14: 63, s15: 64}; + }, TypeError, "Setting Uint8x16 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]; + }, TypeError, "Setting Uint8x16 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 52; + }, TypeError, "Setting Uint8x16 from a number"); +} + +function testUint16x8Alignment() { + assertEq(Uint16x8.byteLength, 16); + assertEq(Uint16x8.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Uint16x8}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testUint16x8Getters() { + // Create a Uint16x8 and check that the getters work: + var f = Uint16x8(11, 22, 33, 44, 55, 66, 77, 88); + assertEq(Uint16x8.extractLane(f, 0), 11); + assertEq(Uint16x8.extractLane(f, 1), 22); + assertEq(Uint16x8.extractLane(f, 2), 33); + assertEq(Uint16x8.extractLane(f, 3), 44); + assertEq(Uint16x8.extractLane(f, 4), 55); + assertEq(Uint16x8.extractLane(f, 5), 66); + assertEq(Uint16x8.extractLane(f, 6), 77); + assertEq(Uint16x8.extractLane(f, 7), 88); + + assertThrowsInstanceOf(() => Uint16x8.extractLane(f, 8), RangeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(Uint32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Uint16x8.extractLane(f, f), TypeError); +} + +function testUint16x8Handles() { + var Array = Uint16x8.array(3); + var array = new Array([Uint16x8(1, 2, 3, 4, 5, 6, 7, 8), + Uint16x8(9, 10, 11, 12, 13, 14, 15, 16), + Uint16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + + // Test that trying to create handle into the interior of a + // Uint16x8 fails. + assertThrowsInstanceOf(function() { + var h = int16.handle(array, 1, 0); + }, TypeError, "Creating a int16 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int16.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int16 handle to elem via move"); +} + +function testUint16x8Reify() { + var Array = Uint16x8.array(3); + var array = new Array([Uint16x8(1, 2, 3, 4, 5, 6, 7, 8), + Uint16x8(9, 10, 11, 12, 13, 14, 15, 16), + Uint16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + + // Test that reading array[1] produces a *copy* of Uint16x8, not an + // alias into the array. + + var f = array[1]; + assertEq(Uint16x8.extractLane(f, 3), 12); + assertEq(Uint16x8.extractLane(array[1], 3), 12); + array[1] = Uint16x8(25, 26, 27, 28, 29, 30, 31, 32); + assertEq(Uint16x8.extractLane(f, 3), 12); + assertEq(Uint16x8.extractLane(array[1], 3), 28); +} + +function testUint16x8Setters() { + var Array = Uint16x8.array(3); + var array = new Array([Uint16x8(1, 2, 3, 4, 5, 6, 7, 8), + Uint16x8(9, 10, 11, 12, 13, 14, 15, 16), + Uint16x8(17, 18, 19, 20, 21, 22, 23, 24)]); + assertEq(Uint16x8.extractLane(array[1], 3), 12); + + // Test that we are allowed to write Uint16x8 values into array, + // but not other things. + + array[1] = Uint16x8(25, 26, 27, 28, 29, 30, 31, 32); + assertEq(Uint16x8.extractLane(array[1], 3), 28); + + assertThrowsInstanceOf(function() { + array[1] = {s0: 25, s1: 26, s2: 27, s3: 28, s4: 29, s5: 30, s6: 31, s7: 32}; + }, TypeError, "Setting Uint16x8 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [25, 26, 27, 28, 29, 30, 31, 32]; + }, TypeError, "Setting Uint16x8 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 28; + }, TypeError, "Setting Uint16x8 from a number"); +} + +function testUint32x4Alignment() { + assertEq(Uint32x4.byteLength, 16); + assertEq(Uint32x4.byteAlignment, 16); + + var Compound = new StructType({c: uint8, d: uint8, f: Uint32x4}); + assertEq(Compound.fieldOffsets.c, 0); + assertEq(Compound.fieldOffsets.d, 1); + assertEq(Compound.fieldOffsets.f, 16); +} + +function testUint32x4Getters() { + // Create a Uint32x4 and check that the getters work: + var f = Uint32x4(11, 22, 33, 44); + assertEq(Uint32x4.extractLane(f, 0), 11); + assertEq(Uint32x4.extractLane(f, 1), 22); + assertEq(Uint32x4.extractLane(f, 2), 33); + assertEq(Uint32x4.extractLane(f, 3), 44); + + assertThrowsInstanceOf(() => Uint32x4.extractLane(f, 4), RangeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(Float32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Uint32x4.extractLane(f, f), TypeError); +} + +function testUint32x4Handles() { + var Array = Uint32x4.array(3); + var array = new Array([Uint32x4(1, 2, 3, 4), + Uint32x4(5, 6, 7, 8), + Uint32x4(9, 10, 11, 12)]); + + // Test that trying to create handle into the interior of a + // Uint32x4 fails. + assertThrowsInstanceOf(function() { + var h = int32.handle(array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via ctor"); + + assertThrowsInstanceOf(function() { + var h = int32.handle(); + Handle.move(h, array, 1, 0); + }, TypeError, "Creating a int32 handle to elem via move"); +} + +function testUint32x4Reify() { + var Array = Uint32x4.array(3); + var array = new Array([Uint32x4(1, 2, 3, 4), + Uint32x4(5, 6, 7, 8), + Uint32x4(9, 10, 11, 12)]); + + // Test that reading array[1] produces a *copy* of Uint32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(Uint32x4.extractLane(f, 3), 8); + assertEq(Uint32x4.extractLane(array[1], 3), 8); + array[1] = Uint32x4(15, 16, 17, 18); + assertEq(Uint32x4.extractLane(f, 3), 8); + assertEq(Uint32x4.extractLane(array[1], 3), 18); +} + +function testUint32x4Setters() { + var Array = Uint32x4.array(3); + var array = new Array([Uint32x4(1, 2, 3, 4), + Uint32x4(5, 6, 7, 8), + Uint32x4(9, 10, 11, 12)]); + assertEq(Uint32x4.extractLane(array[1], 3), 8); + + // Test that we are allowed to write Uint32x4 values into array, + // but not other things. + array[1] = Uint32x4(15, 16, 17, 18); + assertEq(Uint32x4.extractLane(array[1], 3), 18); + + assertThrowsInstanceOf(function() { + array[1] = {x: 15, y: 16, z: 17, w: 18}; + }, TypeError, "Setting Uint32x4 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [15, 16, 17, 18]; + }, TypeError, "Setting Uint32x4 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 22; + }, TypeError, "Setting Uint32x4 from a number"); +} + +function testBool8x16Getters() { + // Create a Bool8x16 and check that the getters work: + var f = Bool8x16(true, false, true, false, true, false, true, false, true, true, false, false, true, true, false, false); + assertEq(Bool8x16.extractLane(f, 0), true); + assertEq(Bool8x16.extractLane(f, 1), false); + assertEq(Bool8x16.extractLane(f, 2), true); + assertEq(Bool8x16.extractLane(f, 3), false); + assertEq(Bool8x16.extractLane(f, 4), true); + assertEq(Bool8x16.extractLane(f, 5), false); + assertEq(Bool8x16.extractLane(f, 6), true); + assertEq(Bool8x16.extractLane(f, 7), false); + assertEq(Bool8x16.extractLane(f, 8), true); + assertEq(Bool8x16.extractLane(f, 9), true); + assertEq(Bool8x16.extractLane(f, 10), false); + assertEq(Bool8x16.extractLane(f, 11), false); + assertEq(Bool8x16.extractLane(f, 12), true); + assertEq(Bool8x16.extractLane(f, 13), true); + assertEq(Bool8x16.extractLane(f, 14), false); + assertEq(Bool8x16.extractLane(f, 15), false); + + assertThrowsInstanceOf(() => Bool8x16.extractLane(f, 16), RangeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(Float32x4(1, 2, 3, 4), 0), TypeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Bool8x16.extractLane(f, f), TypeError); +} + +function testBool8x16Reify() { + var Array = Bool8x16.array(3); + var array = new Array([Bool8x16(true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false), + Bool8x16(false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true), + Bool8x16(true, true, true, true, false, false, false, false, true, true, true, true, false, false, false, false)]); + + // Test that reading array[1] produces a *copy* of Bool8x16, not an + // alias into the array. + + var f = array[1]; + assertEq(Bool8x16.extractLane(f, 2), false); + assertEq(Bool8x16.extractLane(array[1], 2), false); + assertEq(Bool8x16.extractLane(f, 3), true); + assertEq(Bool8x16.extractLane(array[1], 3), true); + array[1] = Bool8x16(true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false); + assertEq(Bool8x16.extractLane(f, 3), true); + assertEq(Bool8x16.extractLane(array[1], 3), false); +} + +function testBool8x16Setters() { + var Array = Bool8x16.array(3); + var array = new Array([Bool8x16(true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false), + Bool8x16(false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true), + Bool8x16(true, true, true, true, false, false, false, false, true, true, true, true, false, false, false, false)]); + + assertEq(Bool8x16.extractLane(array[1], 3), true); + // Test that we are allowed to write Bool8x16 values into array, + // but not other things. + array[1] = Bool8x16(true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false); + assertEq(Bool8x16.extractLane(array[1], 3), false); + assertThrowsInstanceOf(function() { + array[1] = {s0: true, s1: true, s2: true, s3: true, s4: true, s5: true, s6: true, s7: true, + s8: false, s9: false, s10: false, s11: false, s12: false, s13: false, s14: false, s15: false}; + }, TypeError, "Setting Bool8x16 from an object"); + assertThrowsInstanceOf(function() { + array[1] = [true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]; + }, TypeError, "Setting Bool8x16 from an array"); + assertThrowsInstanceOf(function() { + array[1] = false; + }, TypeError, "Setting Bool8x16 from a boolean"); +} + +function testBool16x8Getters() { + // Create a Bool8x16 and check that the getters work: + var f = Bool16x8(true, false, true, false, true, true, false, false); + assertEq(Bool16x8.extractLane(f, 0), true); + assertEq(Bool16x8.extractLane(f, 1), false); + assertEq(Bool16x8.extractLane(f, 2), true); + assertEq(Bool16x8.extractLane(f, 3), false); + assertEq(Bool16x8.extractLane(f, 4), true); + assertEq(Bool16x8.extractLane(f, 5), true); + assertEq(Bool16x8.extractLane(f, 6), false); + assertEq(Bool16x8.extractLane(f, 7), false); + + assertThrowsInstanceOf(() => Bool16x8.extractLane(f, 8), RangeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(Float32x4(1, 2, 3, 4), 0), TypeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Bool16x8.extractLane(f, f), TypeError); +} + +function testBool16x8Reify() { + var Array = Bool16x8.array(3); + var array = new Array([Bool16x8(true, false, true, false, true, false, true, false), + Bool16x8(false, true, false, true, false, true, false, true), + Bool16x8(true, true, true, false, true, true, true, false)]); + // Test that reading array[1] produces a *copy* of Bool16x8, not an + // alias into the array. + var f = array[1]; + assertEq(Bool16x8.extractLane(f, 2), false); + assertEq(Bool16x8.extractLane(array[1], 2), false); + assertEq(Bool16x8.extractLane(f, 3), true); + assertEq(Bool16x8.extractLane(array[1], 3), true); + array[1] = Bool16x8(true, false, true, false, true, false, true, false); + assertEq(Bool16x8.extractLane(f, 3), true); + assertEq(Bool16x8.extractLane(array[1], 3), false); +} + +function testBool16x8Setters() { + var Array = Bool16x8.array(3); + var array = new Array([Bool16x8(true, false, true, false, true, false, true, false), + Bool16x8(false, true, false, true, false, true, false, true), + Bool16x8(true, true, true, false, true, true, true, false)]); + + + assertEq(Bool16x8.extractLane(array[1], 3), true); + // Test that we are allowed to write Bool16x8 values into array, + // but not other things. + array[1] = Bool16x8(true, false, true, false, true, false, true, false); + assertEq(Bool16x8.extractLane(array[1], 3), false); + assertThrowsInstanceOf(function() { + array[1] = {s0: false, s1: true, s2: false, s3: true, s4: false, s5: true, s6: false, s7: true}; + }, TypeError, "Setting Bool16x8 from an object"); + assertThrowsInstanceOf(function() { + array[1] = [true, false, false, true, true, true, false, false]; + }, TypeError, "Setting Bool16x8 from an array"); + assertThrowsInstanceOf(function() { + array[1] = false; + }, TypeError, "Setting Bool16x8 from a boolean"); +} + +function testBool32x4Getters() { + // Create a Bool32x4 and check that the getters work: + var f = Bool32x4(true, false, false, true); + assertEq(Bool32x4.extractLane(f, 0), true); + assertEq(Bool32x4.extractLane(f, 1), false); + assertEq(Bool32x4.extractLane(f, 2), false); + assertEq(Bool32x4.extractLane(f, 3), true); + assertThrowsInstanceOf(() => Bool32x4.extractLane(f, 4), RangeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(Float32x4(1, 2, 3, 4), 0), TypeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Bool32x4.extractLane(f, f), TypeError); +} + +function testBool32x4Reify() { + var Array = Bool32x4.array(3); + var array = new Array([Bool32x4(true, false, false, true), + Bool32x4(true, false, true, false), + Bool32x4(true, true, true, false)]); + + // Test that reading array[1] produces a *copy* of Bool32x4, not an + // alias into the array. + + var f = array[1]; + assertEq(Bool32x4.extractLane(f, 2), true); + assertEq(Bool32x4.extractLane(array[1], 2), true); + assertEq(Bool32x4.extractLane(f, 3), false); + assertEq(Bool32x4.extractLane(array[1], 3), false); + array[1] = Bool32x4(false, true, false, true); + assertEq(Bool32x4.extractLane(f, 3), false); + assertEq(Bool32x4.extractLane(array[1], 3), true); +} + +function testBool32x4Setters() { + var Array = Bool32x4.array(3); + var array = new Array([Bool32x4(true, false, false, true), + Bool32x4(true, false, true, false), + Bool32x4(true, true, true, false)]); + + + assertEq(Bool32x4.extractLane(array[1], 3), false); + // Test that we are allowed to write Bool32x4 values into array, + // but not other things. + array[1] = Bool32x4(false, true, false, true); + assertEq(Bool32x4.extractLane(array[1], 3), true); + assertThrowsInstanceOf(function() { + array[1] = {x: false, y: true, z: false, w: true}; + }, TypeError, "Setting Bool32x4 from an object"); + assertThrowsInstanceOf(function() { + array[1] = [true, false, false, true]; + }, TypeError, "Setting Bool32x4 from an array"); + assertThrowsInstanceOf(function() { + array[1] = false; + }, TypeError, "Setting Bool32x4 from a number"); +} + +function testBool64x2Getters() { + // Create a Bool64x2 and check that the getters work: + var f = Bool64x2(true, false); + assertEq(Bool64x2.extractLane(f, 0), true); + assertEq(Bool64x2.extractLane(f, 1), false); + + assertThrowsInstanceOf(() => Bool64x2.extractLane(f, 2), RangeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(f, -1), RangeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(f, 0.5), RangeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(f, {}), RangeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(Bool32x4(1,2,3,4), 0), TypeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(1, 0), TypeError); + assertThrowsInstanceOf(() => Bool64x2.extractLane(f, f), TypeError); +} + +function testBool64x2Reify() { + var Array = Bool64x2.array(3); + var array = new Array([Bool64x2(true, false), + Bool64x2(false, true), + Bool64x2(true, true)]); + + // Test that reading array[1] produces a *copy* of Bool64x2, not an + // alias into the array. + + var f = array[1]; + assertEq(Bool64x2.extractLane(f, 1), true); + assertEq(Bool64x2.extractLane(array[1], 1), true); + array[1] = Bool64x2(false, false); + assertEq(Bool64x2.extractLane(f, 1), true); + assertEq(Bool64x2.extractLane(array[1], 1), false); +} + +function testBool64x2Setters() { + var Array = Bool64x2.array(3); + var array = new Array([Bool64x2(true, false), + Bool64x2(false, true), + Bool64x2(true, true)]); + assertEq(Bool64x2.extractLane(array[1], 1), true); + + // Test that we are allowed to write Bool64x2 values into array, + // but not other things. + + array[1] = Bool64x2(false, false); + assertEq(Bool64x2.extractLane(array[1], 1), false); + + assertThrowsInstanceOf(function() { + array[1] = {x: false, y: false }; + }, TypeError, "Setting Bool64x2 from an object"); + + assertThrowsInstanceOf(function() { + array[1] = [ false, false ]; + }, TypeError, "Setting Bool64x2 from an array"); + + assertThrowsInstanceOf(function() { + array[1] = 9; + }, TypeError, "Setting Bool64x2 from a number"); +} + + +function test() { + + testFloat32x4Alignment(); + testFloat32x4Getters(); + testFloat32x4Handles(); + testFloat32x4Reify(); + testFloat32x4Setters(); + + testFloat64x2Alignment(); + testFloat64x2Getters(); + testFloat64x2Handles(); + testFloat64x2Reify(); + testFloat64x2Setters(); + + testInt8x16Alignment(); + testInt8x16Getters(); + testInt8x16Handles(); + testInt8x16Reify(); + testInt8x16Setters(); + + testInt16x8Alignment(); + testInt16x8Getters(); + testInt16x8Handles(); + testInt16x8Reify(); + testInt16x8Setters(); + + testInt32x4Alignment(); + testInt32x4Getters(); + testInt32x4Handles(); + testInt32x4Reify(); + testInt32x4Setters(); + + testUint8x16Alignment(); + testUint8x16Getters(); + testUint8x16Handles(); + testUint8x16Reify(); + testUint8x16Setters(); + + testUint16x8Alignment(); + testUint16x8Getters(); + testUint16x8Handles(); + testUint16x8Reify(); + testUint16x8Setters(); + + testUint32x4Alignment(); + testUint32x4Getters(); + testUint32x4Handles(); + testUint32x4Reify(); + testUint32x4Setters(); + + testBool8x16Getters(); + testBool8x16Reify(); + testBool8x16Setters(); + + testBool16x8Getters(); + testBool16x8Reify(); + testBool16x8Setters(); + + testBool32x4Getters(); + testBool32x4Reify(); + testBool32x4Setters(); + + testBool64x2Getters(); + testBool64x2Reify(); + testBool64x2Setters(); + + if (typeof reportCompare === "function") { + reportCompare(true, true); + } +} + +test(); diff --git a/js/src/tests/non262/SIMD/unary-operations.js b/js/src/tests/non262/SIMD/unary-operations.js new file mode 100644 index 000000000000..b04d238743b0 --- /dev/null +++ b/js/src/tests/non262/SIMD/unary-operations.js @@ -0,0 +1,410 @@ +// |reftest| skip-if(!this.hasOwnProperty("SIMD")) +var Float32x4 = SIMD.Float32x4; +var Int8x16 = SIMD.Int8x16; +var Int16x8 = SIMD.Int16x8; +var Int32x4 = SIMD.Int32x4; +var Uint8x16 = SIMD.Uint8x16; +var Uint16x8 = SIMD.Uint16x8; +var Uint32x4 = SIMD.Uint32x4; +var Bool8x16 = SIMD.Bool8x16; +var Bool16x8 = SIMD.Bool16x8; +var Bool32x4 = SIMD.Bool32x4; +var Bool64x2 = SIMD.Bool64x2; + +function testFloat32x4abs() { + function absf(a) { + return Math.abs(Math.fround(a)); + } + + var vals = [ + [-1, 2, -3, 4], + [-1.63, 2.46, -3.17, 4.94], + [NaN, -0, Infinity, -Infinity] + ]; + for (var v of vals) { + assertEqX4(Float32x4.abs(Float32x4(...v)), v.map(absf)); + } +} + +function testFloat32x4neg() { + function negf(a) { + return -1 * Math.fround(a); + } + + var vals = [ + [1, 2, 3, 4], + [0.999, -0.001, 3.78, 4.05], + [NaN, -0, Infinity, -Infinity] + ]; + for (var v of vals) { + assertEqX4(Float32x4.neg(Float32x4(...v)), v.map(negf)); + } +} + +function testFloat32x4reciprocalApproximation() { + function reciprocalf(a) { + return Math.fround(1 / Math.fround(a)); + } + + var vals = [ + [[1, 0.5, 0.25, 0.125], [1, 2, 4, 8]], + [[1.6, 0.8, 0.4, 0.2], [1.6, 0.8, 0.4, 0.2].map(reciprocalf)], + [[NaN, -0, Infinity, -Infinity], [NaN, -Infinity, 0, -0]] + ]; + + for (var [v,w] of vals) { + assertEqX4(Float32x4.reciprocalApproximation(Float32x4(...v)), w); + } +} + +function testFloat32x4reciprocalSqrtApproximation() { + function reciprocalsqrtf(a) { + assertEq(Math.fround(a), a); + return Math.fround(1 / Math.fround(Math.sqrt(a))); + } + + var vals = [ + [[1, 1, 0.25, 0.25], [1, 1, 2, 2]], + [[25, 16, 6.25, 1.5625], [25, 16, 6.25, 1.5625].map(reciprocalsqrtf)], + [[NaN, -0, Infinity, -Infinity], [NaN, -0, Infinity, -Infinity].map(reciprocalsqrtf)], + [[Math.pow(2, 32), Math.pow(2, -32), +0, Math.pow(2, -148)], + [Math.pow(2, -16), Math.pow(2, 16), Infinity, Math.pow(2, 74)]] + ]; + + for (var [v,w] of vals) { + assertEqX4(Float32x4.reciprocalSqrtApproximation(Float32x4(...v)), w); + } +} + +function testFloat32x4sqrt() { + function sqrtf(a) { + return Math.fround(Math.sqrt(Math.fround(a))); + } + + var vals = [ + [[1, 4, 9, 16], [1, 2, 3, 4]], + [[2.7225, 7.3441, 9.4249, -1], [2.7225, 7.3441, 9.4249, -1].map(sqrtf)], + [[NaN, -0, Infinity, -Infinity], [NaN, -0, Infinity, NaN]] + ]; + + for (var [v,w] of vals) { + assertEqX4(Float32x4.sqrt(Float32x4(...v)), w); + } +} + +function testInt8x16neg() { + var vals = [ + [[1, 2, 3, 4, 5, 6, 7, 8, -1, -2, -3, -4, -5, INT8_MAX, INT8_MIN, 0], + [-1, -2, -3, -4, -5, -6, -7, -8, 1, 2, 3, 4, 5, -INT8_MAX, INT8_MIN, 0]] + ]; + for (var [v,w] of vals) { + assertEqX16(Int8x16.neg(Int8x16(...v)), w); + } +} + +function testInt8x16not() { + var vals = [ + [[1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, 0, INT8_MIN, INT8_MAX], + [1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, 0, INT8_MIN, INT8_MAX].map((x) => ~x << 24 >> 24)] + ]; + for (var [v,w] of vals) { + assertEqX16(Int8x16.not(Int8x16(...v)), w); + } +} + +function testInt16x8neg() { + var vals = [ + [[1, 2, 3, -1, -2, 0, INT16_MIN, INT16_MAX], + [-1, -2, -3, 1, 2, 0, INT16_MIN, -INT16_MAX]] + ]; + for (var [v,w] of vals) { + assertEqX8(Int16x8.neg(Int16x8(...v)), w); + } +} + +function testInt16x8not() { + var vals = [ + [[1, 2, 3, -1, -2, 0, INT16_MIN, INT16_MAX], + [1, 2, 3, -1, -2, 0, INT16_MIN, INT16_MAX].map((x) => ~x << 16 >> 16)] + ]; + for (var [v,w] of vals) { + assertEqX8(Int16x8.not(Int16x8(...v)), w); + } +} + +function testInt32x4neg() { + var valsExp = [ + [[1, 2, 3, 4], [-1, -2, -3, -4]], + [[INT32_MAX, INT32_MIN, -0, 0], [-INT32_MAX | 0, -INT32_MIN | 0, 0, 0]] + ]; + for (var [v,w] of valsExp) { + assertEqX4(Int32x4.neg(Int32x4(...v)), w); + } +} + +function testInt32x4not() { + var valsExp = [ + [[1, 2, 3, 4], [-2, -3, -4, -5]], + [[INT32_MAX, INT32_MIN, 0, 0], [~INT32_MAX | 0, ~INT32_MIN | 0, ~0 | 0, ~0 | 0]] + ]; + for (var [v,w] of valsExp) { + assertEqX4(Int32x4.not(Int32x4(...v)), w); + } +} + +function testUint8x16neg() { + var vals = [ + [[ 1, 2, 3, 4, 5, 6, 7, 0, -1, -2, -3, -4, UINT8_MAX, INT8_MAX, 0, 0], + [255, 254, 253, 252, 251, 250, 249, 0, 1, 2, 3, 4, 1, INT8_MAX+2, 0, 0]] + ]; + for (var [v,w] of vals) { + assertEqX16(Uint8x16.neg(Uint8x16(...v)), w); + } +} + +function testUint8x16not() { + var vals = [ + [[1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, 0, INT8_MIN, INT8_MAX], + [1, 2, 3, 4, 5, 6, 7, -1, -2, -3, -4, -5, -6, 0, INT8_MIN, INT8_MAX].map((x) => ~x << 24 >>> 24)] + ]; + for (var [v,w] of vals) { + assertEqX16(Uint8x16.not(Uint8x16(...v)), w); + } +} + +function testUint16x8neg() { + var vals = [ + [[1, 2, UINT16_MAX, -1, -2, 0, INT16_MIN, INT16_MAX], + [1, 2, UINT16_MAX, -1, -2, 0, INT16_MIN, INT16_MAX].map((x) => -x << 16 >>> 16)] + ]; + for (var [v,w] of vals) { + assertEqX8(Uint16x8.neg(Uint16x8(...v)), w); + } +} + +function testUint16x8not() { + var vals = [ + [[1, 2, UINT16_MAX, -1, -2, 0, INT16_MIN, INT16_MAX], + [1, 2, UINT16_MAX, -1, -2, 0, INT16_MIN, INT16_MAX].map((x) => ~x << 16 >>> 16)] + ]; + for (var [v,w] of vals) { + assertEqX8(Uint16x8.not(Uint16x8(...v)), w); + } +} + +function testUint32x4neg() { + var valsExp = [ + [[1, 2, 3, 4], [-1 >>> 0, -2 >>> 0, -3 >>> 0, -4 >>> 0]], + [[INT32_MAX, INT32_MIN, -0, 0], [-INT32_MAX >>> 0, -INT32_MIN >>> 0, 0, 0]] + ]; + for (var [v,w] of valsExp) { + assertEqX4(Uint32x4.neg(Uint32x4(...v)), w); + } +} + +function testUint32x4not() { + var valsExp = [ + [[1, 2, 3, 4], [~1 >>> 0, ~2 >>> 0, ~3 >>> 0, ~4 >>> 0]], + [[INT32_MAX, INT32_MIN, UINT32_MAX, 0], [~INT32_MAX >>> 0, ~INT32_MIN >>> 0, 0, ~0 >>> 0]] + ]; + for (var [v,w] of valsExp) { + assertEqX4(Uint32x4.not(Uint32x4(...v)), w); + } +} + +function testBool8x16not() { + var valsExp = [ + [[true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false], + [false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]], + [[true, true, false, false, true, true, false, false, true, true, false, false, true, true, false, false], + [false, false, true, true, false, false, true, true, false, false, true, true, false, false, true, true]] + ]; + for (var [v,w] of valsExp) { + assertEqX16(Bool8x16.not(Bool8x16(...v)), w); + } +} + +function testBool8x16allTrue() { + var valsExp = [ + [[false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false], false], + [[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true], true], + [[false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true], false], + [[true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true], false], + [[true, false, true, true, true, true, true, true, true, true, true, true, true, true, false, true], false], + [[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false], false], + ]; + for (var [v,w] of valsExp) { + assertEq(Bool8x16.allTrue(Bool8x16(...v)), w); + } +} + +function testBool8x16anyTrue() { + var valsExp = [ + [[false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false], false], + [[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true], true], + [[false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true], true], + [[true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true], true], + [[true, false, true, true, true, true, true, true, true, true, true, true, true, true, false, true], true], + [[true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false], true], + ]; + for (var [v,w] of valsExp) { + assertEq(Bool8x16.anyTrue(Bool8x16(...v)), w); + } +} + +function testBool16x8not() { + var valsExp = [ + [[true, false, true, false, true, false, true, false], [false, true, false, true, false, true, false, true]], + [[true, true, false, false, true, true, false, false], [false, false, true, true, false, false, true, true]] + ]; + for (var [v,w] of valsExp) { + assertEqX8(Bool16x8.not(Bool16x8(...v)), w); + } +} + +function testBool16x8allTrue() { + var valsExp = [ + [[false, false, false, false, false, false, false, false], false], + [[true, true, true, true, true, true, true, true], true], + [[false, true, true, true, true, true, true, true], false], + [[true, false, true, true, true, true, true, true], false], + [[true, true, true, true, true, true, false, true], false], + [[true, true, true, true, true, true, true, false], false], + ]; + for (var [v,w] of valsExp) { + assertEq(Bool16x8.allTrue(Bool16x8(...v)), w); + } +} + +function testBool16x8anyTrue() { + var valsExp = [ + [[false, false, false, false, false, false, false, false], false], + [[true, true, true, true, true, true, true, true], true], + [[false, false, false, false, false, false, false, true], true], + [[false, false, false, false, false, false, true, false], true], + [[false, true, false, false, false, false, false, true], true], + [[true, false, false, false, false, false, false, false], true], + ]; + for (var [v,w] of valsExp) { + assertEq(Bool16x8.anyTrue(Bool16x8(...v)), w); + } +} + +function testBool32x4not() { + var valsExp = [ + [[true, false, true, false], [false, true, false, true]], + [[true, true, false, false], [false, false, true, true]] + ]; + for (var [v,w] of valsExp) { + assertEqX4(Bool32x4.not(Bool32x4(...v)), w); + } +} + +function testBool32x4allTrue() { + var valsExp = [ + [[false, false, false, false], false], + [[true, false, true, false], false], + [[true, true, true, true], true], + [[true, true, false, false], false] + ]; + for (var [v,w] of valsExp) { + assertEq(Bool32x4.allTrue(Bool32x4(...v)), w); + } +} + +function testBool32x4anyTrue() { + var valsExp = [ + [[false, false, false, false], false], + [[true, false, true, false], true], + [[true, true, true, true], true], + [[true, true, false, false], true] + ]; + for (var [v,w] of valsExp) { + assertEq(Bool32x4.anyTrue(Bool32x4(...v)), w); + } +} + +function testBool64x2not() { + var valsExp = [ + [[false, false], [true, true]], + [[false, true], [true, false]], + [[true, false], [false, true]], + [[true, true], [false, false]] + ]; + for (var [v,w] of valsExp) { + assertEqX2(Bool64x2.not(Bool64x2(...v)), w); + } +} + +function testBool64x2allTrue() { + var valsExp = [ + [[false, false], false], + [[false, true], false], + [[true, false], false], + [[true, true], true] + ]; + for (var [v,w] of valsExp) { + assertEq(Bool64x2.allTrue(Bool64x2(...v)), w); + } +} + +function testBool64x2anyTrue() { + var valsExp = [ + [[false, false], false], + [[false, true], true], + [[true, false], true], + [[true, true], true] + ]; + for (var [v,w] of valsExp) { + assertEq(Bool64x2.anyTrue(Bool64x2(...v)), w); + } +} + +function test() { + testFloat32x4abs(); + testFloat32x4neg(); + testFloat32x4reciprocalApproximation(); + testFloat32x4reciprocalSqrtApproximation(); + testFloat32x4sqrt(); + + testInt8x16neg(); + testInt8x16not(); + + testInt16x8neg(); + testInt16x8not(); + + testInt32x4neg(); + testInt32x4not(); + + testUint8x16neg(); + testUint8x16not(); + + testUint16x8neg(); + testUint16x8not(); + + testUint32x4neg(); + testUint32x4not(); + + testBool8x16not(); + testBool8x16allTrue(); + testBool8x16anyTrue(); + + testBool16x8not(); + testBool16x8allTrue(); + testBool16x8anyTrue(); + + testBool32x4not(); + testBool32x4allTrue(); + testBool32x4anyTrue(); + + testBool64x2not(); + testBool64x2allTrue(); + testBool64x2anyTrue(); + + + if (typeof reportCompare === "function") { + reportCompare(true, true); + } +} + +test(); diff --git a/js/src/tests/non262/TypedObject/method_from.js b/js/src/tests/non262/TypedObject/method_from.js index 486d6076841b..81980837cb69 100644 --- a/js/src/tests/non262/TypedObject/method_from.js +++ b/js/src/tests/non262/TypedObject/method_from.js @@ -205,6 +205,9 @@ function fromNonArrayTypedObjects() { var myStruct = new StructType({x: uint32}); var r1 = type.from(new myStruct({x: 42}), j => j); assertTypedEqual(type, r1, new type([0,0,0,0])); + + var r2 = type.from(SIMD.Int32x4(0,0,0,0), j => j); + assertTypedEqual(type, r1, new type([0,0,0,0])); } function runTests() { diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 52059f26b91d..ea8cce6c0fb8 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -928,8 +928,8 @@ ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle buf return true; // Non-prepared-for-asm.js wasm buffers can be detached at any time. - // This error can only be triggered for Atomics on !WASM_HUGE_MEMORY - // so this error is only visible in testing. + // This error can only be triggered for SIMD.js (which isn't shipping) + // on !WASM_HUGE_MEMORY so this error is only visible in testing. if (buffer->isWasm() || buffer->isPreparedForAsmJS()) return false; diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 3594bd985f1b..067002338b61 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -25,6 +25,9 @@ class Debugger; class TypedObjectModuleObject; class LexicalEnvironmentObject; +class SimdTypeDescr; +enum class SimdType; + /* * Global object slots are reserved as follows: * @@ -447,6 +450,17 @@ class GlobalObject : public NativeObject initTypedObjectModule); } + static JSObject* + getOrCreateSimdGlobalObject(JSContext* cx, Handle global) { + return getOrCreateObject(cx, global, APPLICATION_SLOTS + JSProto_SIMD, initSimdObject); + } + + // Get the type descriptor for one of the SIMD types. + // simdType is one of the JS_SIMDTYPEREPR_* constants. + // Implemented in builtin/SIMD.cpp. + static SimdTypeDescr* + getOrCreateSimdTypeDescr(JSContext* cx, Handle global, SimdType simdType); + TypedObjectModuleObject& getTypedObjectModule() const; static JSObject* @@ -765,6 +779,10 @@ class GlobalObject : public NativeObject // Implemented in builtin/TypedObject.cpp static bool initTypedObjectModule(JSContext* cx, Handle global); + // Implemented in builtin/SIMD.cpp + static bool initSimdObject(JSContext* cx, Handle global); + static bool initSimdType(JSContext* cx, Handle global, SimdType simdType); + static bool initStandardClasses(JSContext* cx, Handle global); static bool initSelfHostingBuiltins(JSContext* cx, Handle global, const JSFunctionSpec* builtins); diff --git a/js/src/vm/JSContext.cpp b/js/src/vm/JSContext.cpp index 128d09ed7f70..f488db2cb554 100644 --- a/js/src/vm/JSContext.cpp +++ b/js/src/vm/JSContext.cpp @@ -39,12 +39,6 @@ #include "jit/PcScriptCache.h" #include "js/CharacterEncoding.h" #include "js/Printf.h" -#ifdef JS_SIMULATOR_ARM64 -# include "jit/arm64/vixl/Simulator-vixl.h" -#endif -#ifdef JS_SIMULATOR_ARM -# include "jit/arm/Simulator-arm.h" -#endif #include "util/DoubleToString.h" #include "util/NativeStack.h" #include "util/Windows.h" diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index e57e1885c38d..1186e4cdc4a6 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -29,6 +29,7 @@ #include "builtin/Reflect.h" #include "builtin/RegExp.h" #include "builtin/SelfHostingDefines.h" +#include "builtin/SIMD.h" #include "builtin/Stream.h" #include "builtin/String.h" #include "builtin/TypedObject.h" @@ -2403,6 +2404,19 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_TypedArray_buffer", js::TypedArray_bufferGetter, 1,0), + JS_FN("std_SIMD_Int8x16_extractLane", simd_int8x16_extractLane, 2,0), + JS_FN("std_SIMD_Int16x8_extractLane", simd_int16x8_extractLane, 2,0), + JS_INLINABLE_FN("std_SIMD_Int32x4_extractLane", simd_int32x4_extractLane, 2,0, SimdInt32x4_extractLane), + JS_FN("std_SIMD_Uint8x16_extractLane", simd_uint8x16_extractLane, 2,0), + JS_FN("std_SIMD_Uint16x8_extractLane", simd_uint16x8_extractLane, 2,0), + JS_FN("std_SIMD_Uint32x4_extractLane", simd_uint32x4_extractLane, 2,0), + JS_INLINABLE_FN("std_SIMD_Float32x4_extractLane", simd_float32x4_extractLane,2,0, SimdFloat32x4_extractLane), + JS_FN("std_SIMD_Float64x2_extractLane", simd_float64x2_extractLane, 2,0), + JS_FN("std_SIMD_Bool8x16_extractLane", simd_bool8x16_extractLane, 2,0), + JS_FN("std_SIMD_Bool16x8_extractLane", simd_bool16x8_extractLane, 2,0), + JS_FN("std_SIMD_Bool32x4_extractLane", simd_bool32x4_extractLane, 2,0), + JS_FN("std_SIMD_Bool64x2_extractLane", simd_bool64x2_extractLane, 2,0), + // Helper funtions after this point. JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject), JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject), @@ -2599,6 +2613,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("TypedObjectTypeDescr", js::TypedObjectTypeDescr, 1, 0), JS_FN("ClampToUint8", js::ClampToUint8, 1, 0), JS_FN("GetTypedObjectModule", js::GetTypedObjectModule, 0, 0), + JS_FN("GetSimdTypeDescr", js::GetSimdTypeDescr, 1, 0), JS_INLINABLE_FN("ObjectIsTypeDescr" , js::ObjectIsTypeDescr, 1, 0, IntrinsicObjectIsTypeDescr), diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 824e6c8b740b..e9b8a68ed8cf 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -1802,6 +1802,10 @@ TypedArrayObject::getElement(uint32_t index) case Scalar::Uint8Clamped: return Uint8ClampedArray::getIndexValue(this, index); case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } @@ -1848,6 +1852,10 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) Float64Array::setIndexValue(obj, index, d); return; case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 15348346a91c..59707d2ecc93 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -401,6 +401,11 @@ TypedArrayShift(Scalar::Type viewType) case Scalar::Int64: case Scalar::Float64: return 3; + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: + return 4; default:; } MOZ_CRASH("Unexpected array type"); diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 80a37395cf59..81876662a801 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -28,6 +28,7 @@ #include "jsmath.h" #include "jsutil.h" +#include "builtin/SIMD.h" #include "builtin/String.h" #include "frontend/Parser.h" #include "gc/Policy.h" @@ -124,7 +125,7 @@ class AsmJSGlobal { public: enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, - AtomicsBuiltinFunction, Constant }; + AtomicsBuiltinFunction, Constant, SimdCtor, SimdOp }; enum VarInitKind { InitConstant, InitImport }; enum ConstantKind { GlobalConstant, MathConstant }; @@ -144,6 +145,11 @@ class AsmJSGlobal Scalar::Type viewType_; AsmJSMathBuiltinFunction mathBuiltinFunc_; AsmJSAtomicsBuiltinFunction atomicsBuiltinFunc_; + SimdType simdCtorType_; + struct { + SimdType type_; + SimdOperation which_; + } simdOp; struct { ConstantKind kind_; double value_; @@ -202,6 +208,18 @@ class AsmJSGlobal MOZ_ASSERT(pod.which_ == AtomicsBuiltinFunction); return pod.u.atomicsBuiltinFunc_; } + SimdType simdCtorType() const { + MOZ_ASSERT(pod.which_ == SimdCtor); + return pod.u.simdCtorType_; + } + SimdOperation simdOperation() const { + MOZ_ASSERT(pod.which_ == SimdOp); + return pod.u.simdOp.which_; + } + SimdType simdOperationType() const { + MOZ_ASSERT(pod.which_ == SimdOp); + return pod.u.simdOp.type_; + } ConstantKind constantKind() const { MOZ_ASSERT(pod.which_ == Constant); return pod.u.constant.kind_; @@ -278,6 +296,7 @@ struct AsmJSMetadataCacheablePod uint32_t numFFIs = 0; uint32_t srcLength = 0; uint32_t srcLengthWithRightBrace = 0; + bool usesSimd = false; AsmJSMetadataCacheablePod() = default; }; @@ -801,6 +820,10 @@ ParseVarOrConstStatement(AsmJSParser& parser, ParseNode** var) // out of range: otherwise // Lastly, a literal may be a float literal which is any double or integer // literal coerced with Math.fround. +// +// This class distinguishes between signed and unsigned integer SIMD types like +// Int32x4 and Uint32x4, and so does Type below. The wasm ValType and ExprType +// enums, and the wasm::LitVal class do not. class NumLit { public: @@ -810,17 +833,44 @@ class NumLit BigUnsigned, Double, Float, + Int8x16, + Int16x8, + Int32x4, + Uint8x16, + Uint16x8, + Uint32x4, + Float32x4, + Bool8x16, + Bool16x8, + Bool32x4, OutOfRangeInt = -1 }; private: Which which_; - JS::Value value_; + union U { + JS::Value scalar_; + SimdConstant simd_; + + // |scalar_| has a non-trivial constructor and therefore MUST be + // placement-new'd into existence. + MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS + U() {} + MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS + } u; public: NumLit() = default; - NumLit(Which w, const Value& v) : which_(w), value_(v) {} + NumLit(Which w, const Value& v) : which_(w) { + new (&u.scalar_) Value(v); + MOZ_ASSERT(!isSimd()); + } + + NumLit(Which w, SimdConstant c) : which_(w) { + new (&u.simd_) SimdConstant(c); + MOZ_ASSERT(isSimd()); + } Which which() const { return which_; @@ -828,7 +878,7 @@ class NumLit int32_t toInt32() const { MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt || which_ == BigUnsigned); - return value_.toInt32(); + return u.scalar_.toInt32(); } uint32_t toUint32() const { @@ -837,17 +887,30 @@ class NumLit double toDouble() const { MOZ_ASSERT(which_ == Double); - return value_.toDouble(); + return u.scalar_.toDouble(); } float toFloat() const { MOZ_ASSERT(which_ == Float); - return float(value_.toDouble()); + return float(u.scalar_.toDouble()); } Value scalarValue() const { MOZ_ASSERT(which_ != OutOfRangeInt); - return value_; + return u.scalar_; + } + + bool isSimd() const + { + return which_ == Int8x16 || which_ == Uint8x16 || which_ == Int16x8 || + which_ == Uint16x8 || which_ == Int32x4 || which_ == Uint32x4 || + which_ == Float32x4 || which_ == Bool8x16 || which_ == Bool16x8 || + which_ == Bool32x4; + } + + const SimdConstant& simdValue() const { + MOZ_ASSERT(isSimd()); + return u.simd_; } bool valid() const { @@ -865,6 +928,20 @@ class NumLit return IsPositiveZero(toDouble()); case NumLit::Float: return IsPositiveZero(toFloat()); + case NumLit::Int8x16: + case NumLit::Uint8x16: + case NumLit::Bool8x16: + return simdValue() == SimdConstant::SplatX16(0); + case NumLit::Int16x8: + case NumLit::Uint16x8: + case NumLit::Bool16x8: + return simdValue() == SimdConstant::SplatX8(0); + case NumLit::Int32x4: + case NumLit::Uint32x4: + case NumLit::Bool32x4: + return simdValue() == SimdConstant::SplatX4(0); + case NumLit::Float32x4: + return simdValue() == SimdConstant::SplatX4(0.f); case NumLit::OutOfRangeInt: MOZ_CRASH("can't be here because of valid() check above"); } @@ -881,6 +958,23 @@ class NumLit return LitVal(toFloat()); case NumLit::Double: return LitVal(toDouble()); + case NumLit::Int8x16: + case NumLit::Uint8x16: + return LitVal(simdValue().asInt8x16()); + case NumLit::Int16x8: + case NumLit::Uint16x8: + return LitVal(simdValue().asInt16x8()); + case NumLit::Int32x4: + case NumLit::Uint32x4: + return LitVal(simdValue().asInt32x4()); + case NumLit::Float32x4: + return LitVal(simdValue().asFloat32x4()); + case NumLit::Bool8x16: + return LitVal(simdValue().asInt8x16(), ValType::B8x16); + case NumLit::Bool16x8: + return LitVal(simdValue().asInt16x8(), ValType::B16x8); + case NumLit::Bool32x4: + return LitVal(simdValue().asInt32x4(), ValType::B32x4); case NumLit::OutOfRangeInt:; } MOZ_CRASH("bad literal"); @@ -890,10 +984,17 @@ class NumLit // Represents the type of a general asm.js expression. // // A canonical subset of types representing the coercion targets: Int, Float, -// Double. +// Double, and the SIMD types. This is almost equivalent to wasm::ValType, +// except the integer SIMD types have signed/unsigned variants. // // Void is also part of the canonical subset which then maps to wasm::ExprType. - +// +// Note that while the canonical subset distinguishes signed and unsigned SIMD +// types, it only uses |Int| to represent signed and unsigned 32-bit integers. +// This is because the scalar coersions x|0 and x>>>0 work with any kind of +// integer input, while the SIMD check functions throw a TypeError if the passed +// type doesn't match. +// class Type { public: @@ -903,6 +1004,16 @@ class Type Unsigned = NumLit::BigUnsigned, DoubleLit = NumLit::Double, Float = NumLit::Float, + Int8x16 = NumLit::Int8x16, + Int16x8 = NumLit::Int16x8, + Int32x4 = NumLit::Int32x4, + Uint8x16 = NumLit::Uint8x16, + Uint16x8 = NumLit::Uint16x8, + Uint32x4 = NumLit::Uint32x4, + Float32x4 = NumLit::Float32x4, + Bool8x16 = NumLit::Bool8x16, + Bool16x8 = NumLit::Bool16x8, + Bool32x4 = NumLit::Bool32x4, Double, MaybeDouble, MaybeFloat, @@ -918,6 +1029,22 @@ class Type public: Type() = default; MOZ_IMPLICIT Type(Which w) : which_(w) {} + MOZ_IMPLICIT Type(SimdType type) { + switch (type) { + case SimdType::Int8x16: which_ = Int8x16; return; + case SimdType::Int16x8: which_ = Int16x8; return; + case SimdType::Int32x4: which_ = Int32x4; return; + case SimdType::Uint8x16: which_ = Uint8x16; return; + case SimdType::Uint16x8: which_ = Uint16x8; return; + case SimdType::Uint32x4: which_ = Uint32x4; return; + case SimdType::Float32x4: which_ = Float32x4; return; + case SimdType::Bool8x16: which_ = Bool8x16; return; + case SimdType::Bool16x8: which_ = Bool16x8; return; + case SimdType::Bool32x4: which_ = Bool32x4; return; + default: break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad SimdType"); + } // Map an already canonicalized Type to the return type of a function call. static Type ret(Type t) { @@ -929,7 +1056,7 @@ class Type static Type lit(const NumLit& lit) { MOZ_ASSERT(lit.valid()); Which which = Type::Which(lit.which()); - MOZ_ASSERT(which >= Fixnum && which <= Float); + MOZ_ASSERT(which >= Fixnum && which <= Bool32x4); Type t; t.which_ = which; return t; @@ -955,6 +1082,18 @@ class Type case Void: return Void; + case Int8x16: + case Int16x8: + case Int32x4: + case Uint8x16: + case Uint16x8: + case Uint32x4: + case Float32x4: + case Bool8x16: + case Bool16x8: + case Bool32x4: + return t; + case MaybeDouble: case MaybeFloat: case Floatish: @@ -978,6 +1117,16 @@ class Type case DoubleLit: return isDoubleLit(); case Double: return isDouble(); case Float: return isFloat(); + case Int8x16: return isInt8x16(); + case Int16x8: return isInt16x8(); + case Int32x4: return isInt32x4(); + case Uint8x16: return isUint8x16(); + case Uint16x8: return isUint16x8(); + case Uint32x4: return isUint32x4(); + case Float32x4: return isFloat32x4(); + case Bool8x16: return isBool8x16(); + case Bool16x8: return isBool16x8(); + case Bool32x4: return isBool32x4(); case MaybeDouble: return isMaybeDouble(); case MaybeFloat: return isMaybeFloat(); case Floatish: return isFloatish(); @@ -1041,14 +1190,64 @@ class Type return isDouble() || isSigned(); } + bool isInt8x16() const { + return which_ == Int8x16; + } + + bool isInt16x8() const { + return which_ == Int16x8; + } + + bool isInt32x4() const { + return which_ == Int32x4; + } + + bool isUint8x16() const { + return which_ == Uint8x16; + } + + bool isUint16x8() const { + return which_ == Uint16x8; + } + + bool isUint32x4() const { + return which_ == Uint32x4; + } + + bool isFloat32x4() const { + return which_ == Float32x4; + } + + bool isBool8x16() const { + return which_ == Bool8x16; + } + + bool isBool16x8() const { + return which_ == Bool16x8; + } + + bool isBool32x4() const { + return which_ == Bool32x4; + } + + bool isSimd() const { + return isInt8x16() || isInt16x8() || isInt32x4() || isUint8x16() || isUint16x8() || + isUint32x4() || isFloat32x4() || isBool8x16() || isBool16x8() || isBool32x4(); + } + + bool isUnsignedSimd() const { + return isUint8x16() || isUint16x8() || isUint32x4(); + } + // Check if this is one of the valid types for a function argument. bool isArgType() const { - return isInt() || isFloat() || isDouble(); + return isInt() || isFloat() || isDouble() || (isSimd() && !isUnsignedSimd()); } // Check if this is one of the valid types for a function return value. bool isReturnType() const { - return isSigned() || isFloat() || isDouble() || isVoid(); + return isSigned() || isFloat() || isDouble() || (isSimd() && !isUnsignedSimd()) || + isVoid(); } // Check if this is one of the valid types for a global variable. @@ -1066,7 +1265,7 @@ class Type case Void: return true; default: - return false; + return isSimd(); } } @@ -1082,6 +1281,16 @@ class Type case Float: return ExprType::F32; case Double: return ExprType::F64; case Void: return ExprType::Void; + case Uint8x16: + case Int8x16: return ExprType::I8x16; + case Uint16x8: + case Int16x8: return ExprType::I16x8; + case Uint32x4: + case Int32x4: return ExprType::I32x4; + case Float32x4: return ExprType::F32x4; + case Bool8x16: return ExprType::B8x16; + case Bool16x8: return ExprType::B16x8; + case Bool32x4: return ExprType::B32x4; default: MOZ_CRASH("Need canonical type"); } } @@ -1116,6 +1325,17 @@ class Type case Void: return ExprType::Void; + + case Uint8x16: + case Int8x16: return ExprType::I8x16; + case Uint16x8: + case Int16x8: return ExprType::I16x8; + case Uint32x4: + case Int32x4: return ExprType::I32x4; + case Float32x4: return ExprType::F32x4; + case Bool8x16: return ExprType::B8x16; + case Bool16x8: return ExprType::B16x8; + case Bool32x4: return ExprType::B32x4; } MOZ_CRASH("Invalid Type"); } @@ -1133,6 +1353,16 @@ class Type case Signed: return "signed"; case Unsigned: return "unsigned"; case Intish: return "intish"; + case Int8x16: return "int8x16"; + case Int16x8: return "int16x8"; + case Int32x4: return "int32x4"; + case Uint8x16: return "uint8x16"; + case Uint16x8: return "uint16x8"; + case Uint32x4: return "uint32x4"; + case Float32x4: return "float32x4"; + case Bool8x16: return "bool8x16"; + case Bool16x8: return "bool16x8"; + case Bool32x4: return "bool32x4"; case Void: return "void"; } MOZ_CRASH("Invalid Type"); @@ -1244,7 +1474,9 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator ArrayView, ArrayViewCtor, MathBuiltinFunction, - AtomicsBuiltinFunction + AtomicsBuiltinFunction, + SimdCtor, + SimdOp }; private: @@ -1283,6 +1515,11 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator Scalar::Type viewType_; AsmJSMathBuiltinFunction mathBuiltinFunc_; AsmJSAtomicsBuiltinFunction atomicsBuiltinFunc_; + SimdType simdCtorType_; + struct SimdTypeAndOperation { + SimdType type_; + SimdOperation which_; + } simdOp; // |varOrConst|, through |varOrConst.literalValue_|, has a // non-trivial constructor and therefore MUST be placement-new'd @@ -1349,6 +1586,24 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator MOZ_ASSERT(which_ == AtomicsBuiltinFunction); return u.atomicsBuiltinFunc_; } + bool isSimdCtor() const { + return which_ == SimdCtor; + } + SimdType simdCtorType() const { + MOZ_ASSERT(which_ == SimdCtor); + return u.simdCtorType_; + } + bool isSimdOperation() const { + return which_ == SimdOp; + } + SimdOperation simdOperation() const { + MOZ_ASSERT(which_ == SimdOp); + return u.simdOp.which_; + } + SimdType simdOperationType() const { + MOZ_ASSERT(which_ == SimdOp); + return u.simdOp.type_; + } }; struct MathBuiltin @@ -1438,6 +1693,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator typedef HashMap GlobalMap; typedef HashMap MathNameMap; typedef HashMap AtomicsNameMap; + typedef HashMap SimdOperationNameMap; typedef Vector ArrayViewVector; JSContext* cx_; @@ -1449,6 +1705,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator PropertyName* bufferArgumentName_; MathNameMap standardLibraryMathNames_; AtomicsNameMap standardLibraryAtomicsNames_; + SimdOperationNameMap standardLibrarySimdOpNames_; RootedFunction dummyFunction_; // Validation-internal state: @@ -1460,6 +1717,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator FuncImportMap funcImportMap_; ArrayViewVector arrayViews_; bool atomicsPresent_; + bool simdPresent_; // State used to build the AsmJSModule in finish(): ModuleEnvironment env_; @@ -1491,6 +1749,12 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator return false; return standardLibraryAtomicsNames_.putNew(atom->asPropertyName(), func); } + bool addStandardLibrarySimdOpName(const char* name, SimdOperation op) { + JSAtom* atom = Atomize(cx_, name, strlen(name)); + if (!atom) + return false; + return standardLibrarySimdOpNames_.putNew(atom->asPropertyName(), op); + } bool newSig(FuncType&& sig, uint32_t* sigIndex) { if (env_.types.length() >= MaxTypes) return failCurrentOffset("too many signatures"); @@ -1521,6 +1785,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator bufferArgumentName_(nullptr), standardLibraryMathNames_(cx), standardLibraryAtomicsNames_(cx), + standardLibrarySimdOpNames_(cx), dummyFunction_(cx), validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE), funcDefs_(cx), @@ -1530,6 +1795,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator funcImportMap_(cx), arrayViews_(cx), atomicsPresent_(false), + simdPresent_(false), env_(CompileMode::Once, Tier::Ion, DebugEnabled::False, HasGcTypes::False, cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled() ? Shareable::True @@ -1642,6 +1908,14 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator return false; } +#define ADDSTDLIBSIMDOPNAME(op) || !addStandardLibrarySimdOpName(#op, SimdOperation::Fn_##op) + if (!standardLibrarySimdOpNames_.init() + FORALL_SIMD_ASMJS_OP(ADDSTDLIBSIMDOPNAME)) + { + return false; + } +#undef ADDSTDLIBSIMDOPNAME + // This flows into FunctionBox, so must be tenured. dummyFunction_ = NewScriptedFunction(cx_, 0, JSFunction::INTERPRETED, nullptr, /* proto = */ nullptr, gc::AllocKind::FUNCTION, @@ -1668,6 +1942,7 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator } RootedFunction& dummyFunction() { return dummyFunction_; } + bool supportsSimd() const { return cx_->jitSupportsSimd(); } bool atomicsPresent() const { return atomicsPresent_; } uint32_t minMemoryLength() const { return env_.minMemoryLength; } @@ -1857,6 +2132,43 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator g.pod.u.atomicsBuiltinFunc_ = func; return asmJSMetadata_->asmJSGlobals.append(std::move(g)); } + bool addSimdCtor(PropertyName* var, SimdType type, PropertyName* field) { + simdPresent_ = true; + + UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); + if (!fieldChars) + return false; + + Global* global = validationLifo_.new_(Global::SimdCtor); + if (!global) + return false; + new (&global->u.simdCtorType_) SimdType(type); + if (!globalMap_.putNew(var, global)) + return false; + + AsmJSGlobal g(AsmJSGlobal::SimdCtor, std::move(fieldChars)); + g.pod.u.simdCtorType_ = type; + return asmJSMetadata_->asmJSGlobals.append(std::move(g)); + } + bool addSimdOperation(PropertyName* var, SimdType type, SimdOperation op, PropertyName* field) { + simdPresent_ = true; + + UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); + if (!fieldChars) + return false; + + Global* global = validationLifo_.new_(Global::SimdOp); + if (!global) + return false; + new (&global->u.simdOp) Global::U::SimdTypeAndOperation{ type, op }; + if (!globalMap_.putNew(var, global)) + return false; + + AsmJSGlobal g(AsmJSGlobal::SimdOp, std::move(fieldChars)); + g.pod.u.simdOp.type_ = type; + g.pod.u.simdOp.which_ = op; + return asmJSMetadata_->asmJSGlobals.append(std::move(g)); + } bool addArrayViewCtor(PropertyName* var, Scalar::Type vt, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) @@ -2126,6 +2438,13 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator } return false; } + bool lookupStandardSimdOpName(PropertyName* name, SimdOperation* op) const { + if (SimdOperationNameMap::Ptr p = standardLibrarySimdOpNames_.lookup(name)) { + *op = p->value(); + return true; + } + return false; + } bool startFunctionBodies() { if (!arrayViews_.empty()) @@ -2152,6 +2471,8 @@ class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator if (!env_.funcImportGlobalDataOffsets.resize(funcImportMap_.count())) return nullptr; + asmJSMetadata_->usesSimd = simdPresent_; + MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty()); if (!asmJSMetadata_->asmJSFuncNames.resize(funcImportMap_.count())) return nullptr; @@ -2258,6 +2579,11 @@ IsCoercionCall(ModuleValidator& m, ParseNode* pn, Type* coerceTo, ParseNode** co return true; } + if (global->isSimdOperation() && global->simdOperation() == SimdOperation::Fn_check) { + *coerceTo = global->simdOperationType(); + return true; + } + return false; } @@ -2274,6 +2600,23 @@ IsFloatLiteral(ModuleValidator& m, ParseNode* pn) return IsNumericNonFloatLiteral(coercedExpr); } +static bool +IsSimdTuple(ModuleValidator& m, ParseNode* pn, SimdType* type) +{ + const ModuleValidator::Global* global; + if (!IsCallToGlobal(m, pn, &global)) + return false; + + if (!global->isSimdCtor()) + return false; + + if (CallArgListLength(pn) != GetSimdLanes(global->simdCtorType())) + return false; + + *type = global->simdCtorType(); + return true; +} + static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn); @@ -2283,10 +2626,52 @@ ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn); static inline bool IsLiteralInt(ModuleValidator& m, ParseNode* pn, uint32_t* u32); +static bool +IsSimdLiteral(ModuleValidator& m, ParseNode* pn) +{ + SimdType type; + if (!IsSimdTuple(m, pn, &type)) + return false; + + ParseNode* arg = CallArgList(pn); + unsigned length = GetSimdLanes(type); + for (unsigned i = 0; i < length; i++) { + if (!IsNumericLiteral(m, arg)) + return false; + + uint32_t _; + switch (type) { + case SimdType::Int8x16: + case SimdType::Int16x8: + case SimdType::Int32x4: + case SimdType::Uint8x16: + case SimdType::Uint16x8: + case SimdType::Uint32x4: + case SimdType::Bool8x16: + case SimdType::Bool16x8: + case SimdType::Bool32x4: + if (!IsLiteralInt(m, arg, &_)) + return false; + break; + case SimdType::Float32x4: + if (!IsNumericNonFloatLiteral(arg)) + return false; + break; + default: + MOZ_CRASH("unhandled simd type"); + } + + arg = NextNode(arg); + } + + MOZ_ASSERT(arg == nullptr); + return true; +} + static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn) { - return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn); + return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn) || IsSimdLiteral(m, pn); } // The JS grammar treats -42 as -(42) (i.e., with separate grammar @@ -2308,6 +2693,104 @@ ExtractNumericNonFloatValue(ParseNode* pn, ParseNode** out = nullptr) return NumberNodeValue(pn); } +static NumLit +ExtractSimdValue(ModuleValidator& m, ParseNode* pn) +{ + MOZ_ASSERT(IsSimdLiteral(m, pn)); + + SimdType type = SimdType::Count; + MOZ_ALWAYS_TRUE(IsSimdTuple(m, pn, &type)); + MOZ_ASSERT(CallArgListLength(pn) == GetSimdLanes(type)); + + ParseNode* arg = CallArgList(pn); + switch (type) { + case SimdType::Int8x16: + case SimdType::Uint8x16: { + MOZ_ASSERT(GetSimdLanes(type) == 16); + int8_t val[16]; + for (size_t i = 0; i < 16; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = int8_t(u32); + } + MOZ_ASSERT(arg == nullptr); + NumLit::Which w = type == SimdType::Uint8x16 ? NumLit::Uint8x16 : NumLit::Int8x16; + return NumLit(w, SimdConstant::CreateX16(val)); + } + case SimdType::Int16x8: + case SimdType::Uint16x8: { + MOZ_ASSERT(GetSimdLanes(type) == 8); + int16_t val[8]; + for (size_t i = 0; i < 8; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = int16_t(u32); + } + MOZ_ASSERT(arg == nullptr); + NumLit::Which w = type == SimdType::Uint16x8 ? NumLit::Uint16x8 : NumLit::Int16x8; + return NumLit(w, SimdConstant::CreateX8(val)); + } + case SimdType::Int32x4: + case SimdType::Uint32x4: { + MOZ_ASSERT(GetSimdLanes(type) == 4); + int32_t val[4]; + for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = int32_t(u32); + } + MOZ_ASSERT(arg == nullptr); + NumLit::Which w = type == SimdType::Uint32x4 ? NumLit::Uint32x4 : NumLit::Int32x4; + return NumLit(w, SimdConstant::CreateX4(val)); + } + case SimdType::Float32x4: { + MOZ_ASSERT(GetSimdLanes(type) == 4); + float val[4]; + for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) + val[i] = float(ExtractNumericNonFloatValue(arg)); + MOZ_ASSERT(arg == nullptr); + return NumLit(NumLit::Float32x4, SimdConstant::CreateX4(val)); + } + case SimdType::Bool8x16: { + MOZ_ASSERT(GetSimdLanes(type) == 16); + int8_t val[16]; + for (size_t i = 0; i < 16; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = u32 ? -1 : 0; + } + MOZ_ASSERT(arg == nullptr); + return NumLit(NumLit::Bool8x16, SimdConstant::CreateX16(val)); + } + case SimdType::Bool16x8: { + MOZ_ASSERT(GetSimdLanes(type) == 8); + int16_t val[8]; + for (size_t i = 0; i < 8; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = u32 ? -1 : 0; + } + MOZ_ASSERT(arg == nullptr); + return NumLit(NumLit::Bool16x8, SimdConstant::CreateX8(val)); + } + case SimdType::Bool32x4: { + MOZ_ASSERT(GetSimdLanes(type) == 4); + int32_t val[4]; + for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) { + uint32_t u32; + MOZ_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); + val[i] = u32 ? -1 : 0; + } + MOZ_ASSERT(arg == nullptr); + return NumLit(NumLit::Bool32x4, SimdConstant::CreateX4(val)); + } + default: + break; + } + + MOZ_CRASH("Unexpected SIMD type."); +} + static NumLit ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn) { @@ -2316,10 +2799,13 @@ ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn) if (pn->isKind(ParseNodeKind::Call)) { // Float literals are explicitly coerced and thus the coerced literal may be // any valid (non-float) numeric literal. - MOZ_ASSERT(CallArgListLength(pn) == 1); - pn = CallArgList(pn); - double d = ExtractNumericNonFloatValue(pn); - return NumLit(NumLit::Float, DoubleValue(d)); + if (CallArgListLength(pn) == 1) { + pn = CallArgList(pn); + double d = ExtractNumericNonFloatValue(pn); + return NumLit(NumLit::Float, DoubleValue(d)); + } + + return ExtractSimdValue(m, pn); } double d = ExtractNumericNonFloatValue(pn, &pn); @@ -2365,6 +2851,16 @@ IsLiteralInt(const NumLit& lit, uint32_t* u32) case NumLit::Double: case NumLit::Float: case NumLit::OutOfRangeInt: + case NumLit::Int8x16: + case NumLit::Uint8x16: + case NumLit::Int16x8: + case NumLit::Uint16x8: + case NumLit::Int32x4: + case NumLit::Uint32x4: + case NumLit::Float32x4: + case NumLit::Bool8x16: + case NumLit::Bool16x8: + case NumLit::Bool32x4: return false; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal type"); @@ -2381,6 +2877,138 @@ IsLiteralInt(ModuleValidator& m, ParseNode* pn, uint32_t* u32) namespace { +#define CASE(TYPE, OP) case SimdOperation::Fn_##OP: return MozOp::TYPE##OP; +#define I8x16CASE(OP) CASE(I8x16, OP) +#define I16x8CASE(OP) CASE(I16x8, OP) +#define I32x4CASE(OP) CASE(I32x4, OP) +#define F32x4CASE(OP) CASE(F32x4, OP) +#define B8x16CASE(OP) CASE(B8x16, OP) +#define B16x8CASE(OP) CASE(B16x8, OP) +#define B32x4CASE(OP) CASE(B32x4, OP) +#define ENUMERATE(TYPE, FOR_ALL, DO) \ + switch(op) { \ + case SimdOperation::Constructor: return MozOp::TYPE##Constructor;\ + FOR_ALL(DO) \ + default: break; \ + } + +static inline MozOp +SimdToOp(SimdType type, SimdOperation op) +{ + switch (type) { + case SimdType::Uint8x16: + // Handle the special unsigned opcodes, then fall through to Int8x16. + switch (op) { + case SimdOperation::Fn_addSaturate: return MozOp::I8x16addSaturateU; + case SimdOperation::Fn_subSaturate: return MozOp::I8x16subSaturateU; + case SimdOperation::Fn_extractLane: return MozOp::I8x16extractLaneU; + case SimdOperation::Fn_shiftRightByScalar: return MozOp::I8x16shiftRightByScalarU; + case SimdOperation::Fn_lessThan: return MozOp::I8x16lessThanU; + case SimdOperation::Fn_lessThanOrEqual: return MozOp::I8x16lessThanOrEqualU; + case SimdOperation::Fn_greaterThan: return MozOp::I8x16greaterThanU; + case SimdOperation::Fn_greaterThanOrEqual: return MozOp::I8x16greaterThanOrEqualU; + case SimdOperation::Fn_fromInt8x16Bits: return MozOp::Limit; + default: break; + } + MOZ_FALLTHROUGH; + case SimdType::Int8x16: + // Bitcasts Uint8x16 <--> Int8x16 become noops. + switch (op) { + case SimdOperation::Fn_fromUint8x16Bits: return MozOp::Limit; + case SimdOperation::Fn_fromUint16x8Bits: return MozOp::I8x16fromInt16x8Bits; + case SimdOperation::Fn_fromUint32x4Bits: return MozOp::I8x16fromInt32x4Bits; + default: break; + } + ENUMERATE(I8x16, FORALL_INT8X16_ASMJS_OP, I8x16CASE) + break; + + case SimdType::Uint16x8: + // Handle the special unsigned opcodes, then fall through to Int16x8. + switch(op) { + case SimdOperation::Fn_addSaturate: return MozOp::I16x8addSaturateU; + case SimdOperation::Fn_subSaturate: return MozOp::I16x8subSaturateU; + case SimdOperation::Fn_extractLane: return MozOp::I16x8extractLaneU; + case SimdOperation::Fn_shiftRightByScalar: return MozOp::I16x8shiftRightByScalarU; + case SimdOperation::Fn_lessThan: return MozOp::I16x8lessThanU; + case SimdOperation::Fn_lessThanOrEqual: return MozOp::I16x8lessThanOrEqualU; + case SimdOperation::Fn_greaterThan: return MozOp::I16x8greaterThanU; + case SimdOperation::Fn_greaterThanOrEqual: return MozOp::I16x8greaterThanOrEqualU; + case SimdOperation::Fn_fromInt16x8Bits: return MozOp::Limit; + default: break; + } + MOZ_FALLTHROUGH; + case SimdType::Int16x8: + // Bitcasts Uint16x8 <--> Int16x8 become noops. + switch (op) { + case SimdOperation::Fn_fromUint8x16Bits: return MozOp::I16x8fromInt8x16Bits; + case SimdOperation::Fn_fromUint16x8Bits: return MozOp::Limit; + case SimdOperation::Fn_fromUint32x4Bits: return MozOp::I16x8fromInt32x4Bits; + default: break; + } + ENUMERATE(I16x8, FORALL_INT16X8_ASMJS_OP, I16x8CASE) + break; + + case SimdType::Uint32x4: + // Handle the special unsigned opcodes, then fall through to Int32x4. + switch(op) { + case SimdOperation::Fn_shiftRightByScalar: return MozOp::I32x4shiftRightByScalarU; + case SimdOperation::Fn_lessThan: return MozOp::I32x4lessThanU; + case SimdOperation::Fn_lessThanOrEqual: return MozOp::I32x4lessThanOrEqualU; + case SimdOperation::Fn_greaterThan: return MozOp::I32x4greaterThanU; + case SimdOperation::Fn_greaterThanOrEqual: return MozOp::I32x4greaterThanOrEqualU; + case SimdOperation::Fn_fromFloat32x4: return MozOp::I32x4fromFloat32x4U; + case SimdOperation::Fn_fromInt32x4Bits: return MozOp::Limit; + default: break; + } + MOZ_FALLTHROUGH; + case SimdType::Int32x4: + // Bitcasts Uint32x4 <--> Int32x4 become noops. + switch (op) { + case SimdOperation::Fn_fromUint8x16Bits: return MozOp::I32x4fromInt8x16Bits; + case SimdOperation::Fn_fromUint16x8Bits: return MozOp::I32x4fromInt16x8Bits; + case SimdOperation::Fn_fromUint32x4Bits: return MozOp::Limit; + default: break; + } + ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32x4CASE) + break; + + case SimdType::Float32x4: + switch (op) { + case SimdOperation::Fn_fromUint8x16Bits: return MozOp::F32x4fromInt8x16Bits; + case SimdOperation::Fn_fromUint16x8Bits: return MozOp::F32x4fromInt16x8Bits; + case SimdOperation::Fn_fromUint32x4Bits: return MozOp::F32x4fromInt32x4Bits; + default: break; + } + ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32x4CASE) + break; + + case SimdType::Bool8x16: + ENUMERATE(B8x16, FORALL_BOOL_SIMD_OP, B8x16CASE) + break; + + case SimdType::Bool16x8: + ENUMERATE(B16x8, FORALL_BOOL_SIMD_OP, B16x8CASE) + break; + + case SimdType::Bool32x4: + ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32x4CASE) + break; + + default: break; + } + MOZ_CRASH("unexpected SIMD (type, operator) combination"); +} + +#undef CASE +#undef I8x16CASE +#undef I16x8CASE +#undef I32x4CASE +#undef F32x4CASE +#undef B8x16CASE +#undef B16x8CASE +#undef B32x4CASE +#undef ENUMERATE + typedef Vector NameVector; // Encapsulates the building of an asm bytecode function from an asm.js function @@ -2661,6 +3289,33 @@ class MOZ_STACK_CLASS FunctionValidator case NumLit::Double: return encoder().writeOp(Op::F64Const) && encoder().writeFixedF64(lit.toDouble()); + case NumLit::Int8x16: + case NumLit::Uint8x16: + return encoder().writeOp(MozOp::I8x16Const) && + encoder().writeFixedI8x16(lit.simdValue().asInt8x16()); + case NumLit::Int16x8: + case NumLit::Uint16x8: + return encoder().writeOp(MozOp::I16x8Const) && + encoder().writeFixedI16x8(lit.simdValue().asInt16x8()); + case NumLit::Int32x4: + case NumLit::Uint32x4: + return encoder().writeOp(MozOp::I32x4Const) && + encoder().writeFixedI32x4(lit.simdValue().asInt32x4()); + case NumLit::Float32x4: + return encoder().writeOp(MozOp::F32x4Const) && + encoder().writeFixedF32x4(lit.simdValue().asFloat32x4()); + case NumLit::Bool8x16: + // Boolean vectors use the Int8x16 memory representation. + return encoder().writeOp(MozOp::B8x16Const) && + encoder().writeFixedI8x16(lit.simdValue().asInt8x16()); + case NumLit::Bool16x8: + // Boolean vectors use the Int16x8 memory representation. + return encoder().writeOp(MozOp::B16x8Const) && + encoder().writeFixedI16x8(lit.simdValue().asInt16x8()); + case NumLit::Bool32x4: + // Boolean vectors use the Int32x4 memory representation. + return encoder().writeOp(MozOp::B32x4Const) && + encoder().writeFixedI32x4(lit.simdValue().asInt32x4()); case NumLit::OutOfRangeInt: break; } @@ -2684,6 +3339,12 @@ class MOZ_STACK_CLASS FunctionValidator TokenStreamAnyChars& anyChars = m().tokenStream().anyCharsAccess(); return callSiteLineNums_.append(anyChars.srcCoords.lineNum(pn->pn_pos.begin)); } + MOZ_MUST_USE bool writeSimdOp(SimdType simdType, SimdOperation simdOp) { + MozOp op = SimdToOp(simdType, simdOp); + if (op == MozOp::Limit) + return true; + return encoder().writeOp(op); + } }; } /* anonymous namespace */ @@ -2846,7 +3507,7 @@ CheckTypeAnnotation(ModuleValidator& m, ParseNode* coercionNode, Type* coerceTo, default:; } - return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)"); + return m.fail(coercionNode, "must be of the form +x, x|0, fround(x), or a SIMD check(x)"); } static bool @@ -2961,6 +3622,97 @@ CheckNewArrayView(ModuleValidator& m, PropertyName* varName, ParseNode* newExpr) return m.addArrayView(varName, type, field); } +static bool +IsSimdValidOperationType(SimdType type, SimdOperation op) +{ +#define CASE(op) case SimdOperation::Fn_##op: + switch(type) { + case SimdType::Int8x16: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_INT8X16_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Int16x8: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_INT16X8_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Int32x4: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_INT32X4_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Uint8x16: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromInt8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_INT8X16_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Uint16x8: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromInt16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_INT16X8_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Uint32x4: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromInt32x4Bits: + FORALL_INT32X4_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Float32x4: + switch (op) { + case SimdOperation::Constructor: + case SimdOperation::Fn_fromUint8x16Bits: + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromUint32x4Bits: + FORALL_FLOAT32X4_ASMJS_OP(CASE) return true; + default: return false; + } + break; + case SimdType::Bool8x16: + case SimdType::Bool16x8: + case SimdType::Bool32x4: + switch (op) { + case SimdOperation::Constructor: + FORALL_BOOL_SIMD_OP(CASE) return true; + default: return false; + } + break; + default: + // Unimplemented SIMD type. + return false; + } +#undef CASE +} + static bool CheckGlobalMathImport(ModuleValidator& m, ParseNode* initNode, PropertyName* varName, PropertyName* field) @@ -2993,6 +3745,42 @@ CheckGlobalAtomicsImport(ModuleValidator& m, ParseNode* initNode, PropertyName* return m.addAtomicsBuiltinFunction(varName, func, field); } +static bool +CheckGlobalSimdImport(ModuleValidator& m, ParseNode* initNode, PropertyName* varName, + PropertyName* field) +{ + if (!m.supportsSimd()) + return m.fail(initNode, "SIMD is not supported on this platform"); + + // SIMD constructor, with the form glob.SIMD.[[type]] + SimdType simdType; + if (!IsSimdTypeName(m.cx()->names(), field, &simdType)) + return m.failName(initNode, "'%s' is not a standard SIMD type", field); + + // IsSimdTypeName will return true for any SIMD type supported by the VM. + // + // Since we may not support all of those SIMD types in asm.js, use the + // asm.js-specific IsSimdValidOperationType() to check if this specific + // constructor is supported in asm.js. + if (!IsSimdValidOperationType(simdType, SimdOperation::Constructor)) + return m.failName(initNode, "'%s' is not a supported SIMD type", field); + + return m.addSimdCtor(varName, simdType, field); +} + +static bool +CheckGlobalSimdOperationImport(ModuleValidator& m, const ModuleValidator::Global* global, + ParseNode* initNode, PropertyName* varName, PropertyName* opName) +{ + SimdType simdType = global->simdCtorType(); + SimdOperation simdOp; + if (!m.lookupStandardSimdOpName(opName, &simdOp)) + return m.failName(initNode, "'%s' is not a standard SIMD operation", opName); + if (!IsSimdValidOperationType(simdType, simdOp)) + return m.failName(initNode, "'%s' is not an operation supported by the SIMD type", opName); + return m.addSimdOperation(varName, simdType, simdOp, opName); +} + static bool CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName, ParseNode* initNode) { @@ -3001,7 +3789,7 @@ CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName, ParseNode* initN if (base->isKind(ParseNodeKind::Dot)) { ParseNode* global = DotBase(base); - PropertyName* mathOrAtomics = DotMember(base); + PropertyName* mathOrAtomicsOrSimd = DotMember(base); PropertyName* globalName = m.globalArgumentName(); if (!globalName) @@ -3015,11 +3803,13 @@ CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName, ParseNode* initN return m.failName(base, "expecting %s.*", globalName); } - if (mathOrAtomics == m.cx()->names().Math) + if (mathOrAtomicsOrSimd == m.cx()->names().Math) return CheckGlobalMathImport(m, initNode, varName, field); - if (mathOrAtomics == m.cx()->names().Atomics) + if (mathOrAtomicsOrSimd == m.cx()->names().Atomics) return CheckGlobalAtomicsImport(m, initNode, varName, field); - return m.failName(base, "expecting %s.{Math|Atomics}", globalName); + if (mathOrAtomicsOrSimd == m.cx()->names().SIMD) + return CheckGlobalSimdImport(m, initNode, varName, field); + return m.failName(base, "expecting %s.{Math|SIMD}", globalName); } if (!base->isKind(ParseNodeKind::Name)) @@ -3038,10 +3828,17 @@ CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName, ParseNode* initN return m.failName(initNode, "'%s' is not a standard constant or typed array name", field); } - if (base->name() != m.importArgumentName()) - return m.fail(base, "expected global or import name"); + if (base->name() == m.importArgumentName()) + return m.addFFI(varName, field); - return m.addFFI(varName, field); + const ModuleValidator::Global* global = m.lookupGlobal(base->name()); + if (!global) + return m.failName(initNode, "%s not found in module global scope", base->name()); + + if (!global->isSimdCtor()) + return m.failName(base, "expecting SIMD constructor name, got %s", field); + + return CheckGlobalSimdOperationImport(m, global, initNode, varName, field); } static bool @@ -3340,6 +4137,8 @@ CheckVarRef(FunctionValidator& f, ParseNode* varRef, Type* type) case ModuleValidator::Global::Table: case ModuleValidator::Global::ArrayView: case ModuleValidator::Global::ArrayViewCtor: + case ModuleValidator::Global::SimdCtor: + case ModuleValidator::Global::SimdOp: break; } return f.failName(varRef, "'%s' may not be accessed by ordinary expressions", name); @@ -3359,10 +4158,12 @@ IsLiteralOrConstInt(FunctionValidator& f, ParseNode* pn, uint32_t* u32) } static const int32_t NoMask = -1; +static const bool YesSimd = true; +static const bool NoSimd = false; static bool CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, - Scalar::Type* viewType) + bool isSimd, Scalar::Type* viewType) { if (!viewName->isKind(ParseNodeKind::Name)) return f.fail(viewName, "base of array access must be a typed array view name"); @@ -3376,7 +4177,7 @@ CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr uint32_t index; if (IsLiteralOrConstInt(f, indexExpr, &index)) { uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType); - uint64_t width = TypedArrayElemSize(*viewType); + uint64_t width = isSimd ? Simd128DataSize : TypedArrayElemSize(*viewType); if (!f.m().tryConstantAccess(byteOffset, width)) return f.fail(indexExpr, "constant index out of range"); @@ -3408,8 +4209,8 @@ CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr if (!pointerType.isIntish()) return f.failf(pointerNode, "%s is not a subtype of int", pointerType.toChars()); } else { - // For legacy scalar access compatibility, accept Int8/Uint8 accesses - // with no shift. + // For SIMD access, and legacy scalar access compatibility, accept + // Int8/Uint8 accesses with no shift. if (TypedArrayShift(*viewType) != 0) return f.fail(indexExpr, "index expression isn't shifted; must be an Int8/Uint8 access"); @@ -3420,12 +4221,18 @@ CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr Type pointerType; if (!CheckExpr(f, pointerNode, &pointerType)) return false; - if (!pointerType.isInt()) - return f.failf(pointerNode, "%s is not a subtype of int", pointerType.toChars()); + + if (isSimd) { + if (!pointerType.isIntish()) + return f.failf(pointerNode, "%s is not a subtype of intish", pointerType.toChars()); + } else { + if (!pointerType.isInt()) + return f.failf(pointerNode, "%s is not a subtype of int", pointerType.toChars()); + } } - // Don't generate the mask op if there is no need for it which could happen - // for a shift of zero. + // Don't generate the mask op if there is no need for it which could happen for + // a shift of zero or a SIMD access. if (mask != NoMask) { return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And); @@ -3434,6 +4241,13 @@ CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr return true; } +static bool +CheckAndPrepareArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, + bool isSimd, Scalar::Type* viewType) +{ + return CheckArrayAccess(f, viewName, indexExpr, isSimd, viewType); +} + static bool WriteArrayAccessFlags(FunctionValidator& f, Scalar::Type viewType) { @@ -3455,7 +4269,7 @@ CheckLoadArray(FunctionValidator& f, ParseNode* elem, Type* type) { Scalar::Type viewType; - if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) + if (!CheckAndPrepareArrayAccess(f, ElemBase(elem), ElemIndex(elem), NoSimd, &viewType)) return false; switch (viewType) { @@ -3498,7 +4312,7 @@ static bool CheckStoreArray(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type) { Scalar::Type viewType; - if (!CheckArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType)) + if (!CheckAndPrepareArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), NoSimd, &viewType)) return false; Type rhsType; @@ -3789,7 +4603,7 @@ static bool CheckSharedArrayAtomicAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, Scalar::Type* viewType) { - if (!CheckArrayAccess(f, viewName, indexExpr, viewType)) + if (!CheckAndPrepareArrayAccess(f, viewName, indexExpr, NoSimd, viewType)) return false; // The global will be sane, CheckArrayAccess checks it. @@ -4096,7 +4910,10 @@ static bool CheckIsArgType(FunctionValidator& f, ParseNode* argNode, Type type) { if (!type.isArgType()) - return f.failf(argNode, "%s is not a subtype of int, float, or double", type.toChars()); + return f.failf(argNode, + "%s is not a subtype of int, float, double, or an allowed SIMD type", + type.toChars()); + return true; } @@ -4227,6 +5044,8 @@ CheckFFICall(FunctionValidator& f, ParseNode* callNode, unsigned ffiIndex, Type if (ret.isFloat()) return f.fail(callNode, "FFI calls can't return float"); + if (ret.isSimd()) + return f.fail(callNode, "FFI calls can't return SIMD values"); ValTypeVector args; if (!CheckCallArgs(f, callNode, &args)) @@ -4282,6 +5101,9 @@ CheckCoercionArg(FunctionValidator& f, ParseNode* arg, Type expected, Type* type if (expected.isFloat()) { if (!CheckFloatCoercionArg(f, arg, argType)) return false; + } else if (expected.isSimd()) { + if (!(argType <= expected)) + return f.fail(arg, "argument to SIMD coercion isn't from the correct SIMD type"); } else { MOZ_CRASH("not call coercions"); } @@ -4385,6 +5207,635 @@ CheckMathBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSMathBuiltin return true; } +namespace { +// Include CheckSimdCallArgs in unnamed namespace to avoid MSVC name lookup bug. + +template +static bool +CheckSimdCallArgs(FunctionValidator& f, ParseNode* call, unsigned expectedArity, + const CheckArgOp& checkArg) +{ + unsigned numArgs = CallArgListLength(call); + if (numArgs != expectedArity) + return f.failf(call, "expected %u arguments to SIMD call, got %u", expectedArity, numArgs); + + ParseNode* arg = CallArgList(call); + for (size_t i = 0; i < numArgs; i++, arg = NextNode(arg)) { + MOZ_ASSERT(!!arg); + Type argType; + if (!CheckExpr(f, arg, &argType)) + return false; + if (!checkArg(f, arg, i, argType)) + return false; + } + + return true; +} + + +class CheckArgIsSubtypeOf +{ + Type formalType_; + + public: + explicit CheckArgIsSubtypeOf(SimdType t) : formalType_(t) {} + + bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const + { + if (!(actualType <= formalType_)) { + return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), + formalType_.toChars()); + } + return true; + } +}; + +static inline Type +SimdToCoercedScalarType(SimdType t) +{ + switch (t) { + case SimdType::Int8x16: + case SimdType::Int16x8: + case SimdType::Int32x4: + case SimdType::Uint8x16: + case SimdType::Uint16x8: + case SimdType::Uint32x4: + case SimdType::Bool8x16: + case SimdType::Bool16x8: + case SimdType::Bool32x4: + return Type::Intish; + case SimdType::Float32x4: + return Type::Floatish; + default: + break; + } + MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected SIMD type"); +} + +class CheckSimdScalarArgs +{ + SimdType simdType_; + Type formalType_; + + public: + explicit CheckSimdScalarArgs(SimdType simdType) + : simdType_(simdType), formalType_(SimdToCoercedScalarType(simdType)) + {} + + bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const + { + if (!(actualType <= formalType_)) { + // As a special case, accept doublelit arguments to float32x4 ops by + // re-emitting them as float32 constants. + if (simdType_ != SimdType::Float32x4 || !actualType.isDoubleLit()) { + return f.failf(arg, "%s is not a subtype of %s%s", + actualType.toChars(), formalType_.toChars(), + simdType_ == SimdType::Float32x4 ? " or doublelit" : ""); + } + + // We emitted a double literal and actually want a float32. + return f.encoder().writeOp(Op::F32DemoteF64); + } + + return true; + } +}; + +class CheckSimdSelectArgs +{ + Type formalType_; + Type maskType_; + + public: + explicit CheckSimdSelectArgs(SimdType t) : formalType_(t), maskType_(GetBooleanSimdType(t)) {} + + bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const + { + // The first argument is the boolean selector, the next two are the + // values to choose from. + Type wantedType = argIndex == 0 ? maskType_ : formalType_; + + if (!(actualType <= wantedType)) { + return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), + wantedType.toChars()); + } + return true; + } +}; + +class CheckSimdVectorScalarArgs +{ + SimdType formalSimdType_; + + public: + explicit CheckSimdVectorScalarArgs(SimdType t) : formalSimdType_(t) {} + + bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const + { + MOZ_ASSERT(argIndex < 2); + if (argIndex == 0) { + // First argument is the vector + if (!(actualType <= Type(formalSimdType_))) { + return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), + Type(formalSimdType_).toChars()); + } + + return true; + } + + // Second argument is the scalar + return CheckSimdScalarArgs(formalSimdType_)(f, arg, argIndex, actualType); + } +}; + +} // namespace + +static bool +CheckSimdUnary(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type* type) +{ + if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) + return false; + if (!f.writeSimdOp(opType, op)) + return false; + *type = opType; + return true; +} + +static bool +CheckSimdBinaryShift(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type *type) +{ + if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType))) + return false; + if (!f.writeSimdOp(opType, op)) + return false; + *type = opType; + return true; +} + +static bool +CheckSimdBinaryComp(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type *type) +{ + if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType))) + return false; + if (!f.writeSimdOp(opType, op)) + return false; + *type = GetBooleanSimdType(opType); + return true; +} + +static bool +CheckSimdBinary(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type* type) +{ + if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType))) + return false; + if (!f.writeSimdOp(opType, op)) + return false; + *type = opType; + return true; +} + +static bool +CheckSimdExtractLane(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + switch (opType) { + case SimdType::Int8x16: + case SimdType::Int16x8: + case SimdType::Int32x4: *type = Type::Signed; break; + case SimdType::Uint8x16: + case SimdType::Uint16x8: + case SimdType::Uint32x4: *type = Type::Unsigned; break; + case SimdType::Float32x4: *type = Type::Float; break; + case SimdType::Bool8x16: + case SimdType::Bool16x8: + case SimdType::Bool32x4: *type = Type::Int; break; + default: MOZ_CRASH("unhandled simd type"); + } + + unsigned numArgs = CallArgListLength(call); + if (numArgs != 2) + return f.failf(call, "expected 2 arguments to SIMD extract, got %u", numArgs); + + ParseNode* arg = CallArgList(call); + + // First argument is the vector + Type vecType; + if (!CheckExpr(f, arg, &vecType)) + return false; + if (!(vecType <= Type(opType))) { + return f.failf(arg, "%s is not a subtype of %s", vecType.toChars(), + Type(opType).toChars()); + } + + arg = NextNode(arg); + + // Second argument is the lane < vector length + uint32_t lane; + if (!IsLiteralOrConstInt(f, arg, &lane)) + return f.failf(arg, "lane selector should be a constant integer literal"); + if (lane >= GetSimdLanes(opType)) + return f.failf(arg, "lane selector should be in bounds"); + + if (!f.writeSimdOp(opType, SimdOperation::Fn_extractLane)) + return false; + if (!f.encoder().writeVarU32(lane)) + return false; + return true; +} + +static bool +CheckSimdReplaceLane(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + unsigned numArgs = CallArgListLength(call); + if (numArgs != 3) + return f.failf(call, "expected 2 arguments to SIMD replace, got %u", numArgs); + + ParseNode* arg = CallArgList(call); + + // First argument is the vector + Type vecType; + if (!CheckExpr(f, arg, &vecType)) + return false; + if (!(vecType <= Type(opType))) { + return f.failf(arg, "%s is not a subtype of %s", vecType.toChars(), + Type(opType).toChars()); + } + + arg = NextNode(arg); + + // Second argument is the lane < vector length + uint32_t lane; + if (!IsLiteralOrConstInt(f, arg, &lane)) + return f.failf(arg, "lane selector should be a constant integer literal"); + if (lane >= GetSimdLanes(opType)) + return f.failf(arg, "lane selector should be in bounds"); + + arg = NextNode(arg); + + // Third argument is the scalar + Type scalarType; + if (!CheckExpr(f, arg, &scalarType)) + return false; + if (!(scalarType <= SimdToCoercedScalarType(opType))) { + if (opType == SimdType::Float32x4 && scalarType.isDoubleLit()) { + if (!f.encoder().writeOp(Op::F32DemoteF64)) + return false; + } else { + return f.failf(arg, "%s is not the correct type to replace an element of %s", + scalarType.toChars(), vecType.toChars()); + } + } + + if (!f.writeSimdOp(opType, SimdOperation::Fn_replaceLane)) + return false; + if (!f.encoder().writeVarU32(lane)) + return false; + *type = opType; + return true; +} + +typedef bool Bitcast; + +namespace { +// Include CheckSimdCast in unnamed namespace to avoid MSVC name lookup bug (due to the use of Type). + +static bool +CheckSimdCast(FunctionValidator& f, ParseNode* call, SimdType fromType, SimdType toType, + SimdOperation op, Type* type) +{ + if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(fromType))) + return false; + if (!f.writeSimdOp(toType, op)) + return false; + *type = toType; + return true; +} + +} // namespace + +static bool +CheckSimdShuffleSelectors(FunctionValidator& f, ParseNode* lane, + mozilla::Array& lanes, unsigned numLanes, unsigned maxLane) +{ + for (unsigned i = 0; i < numLanes; i++, lane = NextNode(lane)) { + uint32_t u32; + if (!IsLiteralInt(f.m(), lane, &u32)) + return f.failf(lane, "lane selector should be a constant integer literal"); + if (u32 >= maxLane) + return f.failf(lane, "lane selector should be less than %u", maxLane); + lanes[i] = uint8_t(u32); + } + return true; +} + +static bool +CheckSimdSwizzle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + const unsigned numLanes = GetSimdLanes(opType); + unsigned numArgs = CallArgListLength(call); + if (numArgs != 1 + numLanes) + return f.failf(call, "expected %u arguments to SIMD swizzle, got %u", 1 + numLanes, + numArgs); + + Type retType = opType; + ParseNode* vec = CallArgList(call); + Type vecType; + if (!CheckExpr(f, vec, &vecType)) + return false; + if (!(vecType <= retType)) + return f.failf(vec, "%s is not a subtype of %s", vecType.toChars(), retType.toChars()); + + if (!f.writeSimdOp(opType, SimdOperation::Fn_swizzle)) + return false; + + mozilla::Array lanes; + if (!CheckSimdShuffleSelectors(f, NextNode(vec), lanes, numLanes, numLanes)) + return false; + + for (unsigned i = 0; i < numLanes; i++) { + if (!f.encoder().writeFixedU8(lanes[i])) + return false; + } + + *type = retType; + return true; +} + +static bool +CheckSimdShuffle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + const unsigned numLanes = GetSimdLanes(opType); + unsigned numArgs = CallArgListLength(call); + if (numArgs != 2 + numLanes) + return f.failf(call, "expected %u arguments to SIMD shuffle, got %u", 2 + numLanes, + numArgs); + + Type retType = opType; + ParseNode* arg = CallArgList(call); + for (unsigned i = 0; i < 2; i++, arg = NextNode(arg)) { + Type type; + if (!CheckExpr(f, arg, &type)) + return false; + if (!(type <= retType)) + return f.failf(arg, "%s is not a subtype of %s", type.toChars(), retType.toChars()); + } + + if (!f.writeSimdOp(opType, SimdOperation::Fn_shuffle)) + return false; + + mozilla::Array lanes; + if (!CheckSimdShuffleSelectors(f, arg, lanes, numLanes, 2 * numLanes)) + return false; + + for (unsigned i = 0; i < numLanes; i++) { + if (!f.encoder().writeFixedU8(uint8_t(lanes[i]))) + return false; + } + + *type = retType; + return true; +} + +static bool +CheckSimdLoadStoreArgs(FunctionValidator& f, ParseNode* call, Scalar::Type* viewType) +{ + ParseNode* view = CallArgList(call); + if (!view->isKind(ParseNodeKind::Name)) + return f.fail(view, "expected Uint8Array view as SIMD.*.load/store first argument"); + + ParseNode* indexExpr = NextNode(view); + + if (!CheckAndPrepareArrayAccess(f, view, indexExpr, YesSimd, viewType)) + return false; + + if (*viewType != Scalar::Uint8) + return f.fail(view, "expected Uint8Array view as SIMD.*.load/store first argument"); + + return true; +} + +static bool +CheckSimdLoad(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type* type) +{ + unsigned numArgs = CallArgListLength(call); + if (numArgs != 2) + return f.failf(call, "expected 2 arguments to SIMD load, got %u", numArgs); + + Scalar::Type viewType; + if (!CheckSimdLoadStoreArgs(f, call, &viewType)) + return false; + + if (!f.writeSimdOp(opType, op)) + return false; + + if (!WriteArrayAccessFlags(f, viewType)) + return false; + + *type = opType; + return true; +} + +static bool +CheckSimdStore(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, + Type* type) +{ + unsigned numArgs = CallArgListLength(call); + if (numArgs != 3) + return f.failf(call, "expected 3 arguments to SIMD store, got %u", numArgs); + + Scalar::Type viewType; + if (!CheckSimdLoadStoreArgs(f, call, &viewType)) + return false; + + Type retType = opType; + ParseNode* vecExpr = NextNode(NextNode(CallArgList(call))); + Type vecType; + if (!CheckExpr(f, vecExpr, &vecType)) + return false; + + if (!f.writeSimdOp(opType, op)) + return false; + + if (!WriteArrayAccessFlags(f, viewType)) + return false; + + if (!(vecType <= retType)) + return f.failf(vecExpr, "%s is not a subtype of %s", vecType.toChars(), retType.toChars()); + + *type = vecType; + return true; +} + +static bool +CheckSimdSelect(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + if (!CheckSimdCallArgs(f, call, 3, CheckSimdSelectArgs(opType))) + return false; + if (!f.writeSimdOp(opType, SimdOperation::Fn_select)) + return false; + *type = opType; + return true; +} + +static bool +CheckSimdAllTrue(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) + return false; + if (!f.writeSimdOp(opType, SimdOperation::Fn_allTrue)) + return false; + *type = Type::Int; + return true; +} + +static bool +CheckSimdAnyTrue(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) + return false; + if (!f.writeSimdOp(opType, SimdOperation::Fn_anyTrue)) + return false; + *type = Type::Int; + return true; +} + +static bool +CheckSimdCheck(FunctionValidator& f, ParseNode* call, Type* type) +{ + Type coerceTo; + ParseNode* argNode; + if (!IsCoercionCall(f.m(), call, &coerceTo, &argNode)) + return f.failf(call, "expected 1 argument in call to check"); + return CheckCoercionArg(f, argNode, coerceTo, type); +} + +static bool +CheckSimdSplat(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) +{ + if (!CheckSimdCallArgs(f, call, 1, CheckSimdScalarArgs(opType))) + return false; + if (!f.writeSimdOp(opType, SimdOperation::Fn_splat)) + return false; + *type = opType; + return true; +} + +static bool +CheckSimdOperationCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, + Type* type) +{ + MOZ_ASSERT(global->isSimdOperation()); + + SimdType opType = global->simdOperationType(); + + switch (SimdOperation op = global->simdOperation()) { + case SimdOperation::Fn_check: + return CheckSimdCheck(f, call, type); + +#define _CASE(OP) case SimdOperation::Fn_##OP: + FOREACH_SHIFT_SIMD_OP(_CASE) + return CheckSimdBinaryShift(f, call, opType, op, type); + + FOREACH_COMP_SIMD_OP(_CASE) + return CheckSimdBinaryComp(f, call, opType, op, type); + + FOREACH_NUMERIC_SIMD_BINOP(_CASE) + FOREACH_FLOAT_SIMD_BINOP(_CASE) + FOREACH_BITWISE_SIMD_BINOP(_CASE) + FOREACH_SMINT_SIMD_BINOP(_CASE) + return CheckSimdBinary(f, call, opType, op, type); +#undef _CASE + + case SimdOperation::Fn_extractLane: + return CheckSimdExtractLane(f, call, opType, type); + case SimdOperation::Fn_replaceLane: + return CheckSimdReplaceLane(f, call, opType, type); + + case SimdOperation::Fn_fromInt8x16Bits: + return CheckSimdCast(f, call, SimdType::Int8x16, opType, op, type); + case SimdOperation::Fn_fromUint8x16Bits: + return CheckSimdCast(f, call, SimdType::Uint8x16, opType, op, type); + case SimdOperation::Fn_fromInt16x8Bits: + return CheckSimdCast(f, call, SimdType::Int16x8, opType, op, type); + case SimdOperation::Fn_fromUint16x8Bits: + return CheckSimdCast(f, call, SimdType::Uint16x8, opType, op, type); + case SimdOperation::Fn_fromInt32x4: + case SimdOperation::Fn_fromInt32x4Bits: + return CheckSimdCast(f, call, SimdType::Int32x4, opType, op, type); + case SimdOperation::Fn_fromUint32x4: + case SimdOperation::Fn_fromUint32x4Bits: + return CheckSimdCast(f, call, SimdType::Uint32x4, opType, op, type); + case SimdOperation::Fn_fromFloat32x4: + case SimdOperation::Fn_fromFloat32x4Bits: + return CheckSimdCast(f, call, SimdType::Float32x4, opType, op, type); + + case SimdOperation::Fn_abs: + case SimdOperation::Fn_neg: + case SimdOperation::Fn_not: + case SimdOperation::Fn_sqrt: + case SimdOperation::Fn_reciprocalApproximation: + case SimdOperation::Fn_reciprocalSqrtApproximation: + return CheckSimdUnary(f, call, opType, op, type); + + case SimdOperation::Fn_swizzle: + return CheckSimdSwizzle(f, call, opType, type); + case SimdOperation::Fn_shuffle: + return CheckSimdShuffle(f, call, opType, type); + + case SimdOperation::Fn_load: + case SimdOperation::Fn_load1: + case SimdOperation::Fn_load2: + return CheckSimdLoad(f, call, opType, op, type); + case SimdOperation::Fn_store: + case SimdOperation::Fn_store1: + case SimdOperation::Fn_store2: + return CheckSimdStore(f, call, opType, op, type); + + case SimdOperation::Fn_select: + return CheckSimdSelect(f, call, opType, type); + + case SimdOperation::Fn_splat: + return CheckSimdSplat(f, call, opType, type); + + case SimdOperation::Fn_allTrue: + return CheckSimdAllTrue(f, call, opType, type); + case SimdOperation::Fn_anyTrue: + return CheckSimdAnyTrue(f, call, opType, type); + + case SimdOperation::Fn_load3: + case SimdOperation::Fn_store3: + return f.fail(call, "asm.js does not support 3-element SIMD loads or stores"); + + case SimdOperation::Constructor: + MOZ_CRASH("constructors are handled in CheckSimdCtorCall"); + case SimdOperation::Fn_fromFloat64x2Bits: + MOZ_CRASH("NYI"); + } + MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall"); +} + +static bool +CheckSimdCtorCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, + Type* type) +{ + MOZ_ASSERT(call->isKind(ParseNodeKind::Call)); + + SimdType simdType = global->simdCtorType(); + unsigned length = GetSimdLanes(simdType); + if (!CheckSimdCallArgs(f, call, length, CheckSimdScalarArgs(simdType))) + return false; + + if (!f.writeSimdOp(simdType, SimdOperation::Constructor)) + return false; + + *type = simdType; + return true; +} + static bool CheckUncoercedCall(FunctionValidator& f, ParseNode* expr, Type* type) { @@ -4396,12 +5847,16 @@ CheckUncoercedCall(FunctionValidator& f, ParseNode* expr, Type* type) return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type); if (global->isAtomicsFunction()) return CheckAtomicsBuiltinCall(f, expr, global->atomicsBuiltinFunction(), type); + if (global->isSimdCtor()) + return CheckSimdCtorCall(f, expr, global, type); + if (global->isSimdOperation()) + return CheckSimdOperationCall(f, expr, global, type); } return f.fail(expr, "all function calls must either be calls to standard lib math functions, " - "standard atomic functions ignored (via f(); or comma-expression), coerced" - " to signed (via f()|0), coerced to float (via fround(f())) or coerced to " - "double (via +f())"); + "standard atomic functions, standard SIMD constructors or operations, " + "ignored (via f(); or comma-expression), coerced to signed (via f()|0), " + "coerced to float (via fround(f())) or coerced to double (via +f())"); } static bool @@ -4444,7 +5899,10 @@ CoerceResult(FunctionValidator& f, ParseNode* expr, Type expected, Type actual, } break; default: - MOZ_CRASH("unexpected uncoerced result type"); + MOZ_ASSERT(expected.isSimd(), "Incomplete switch"); + if (actual != expected) + return f.failf(expr, "got type %s, expected %s", actual.toChars(), expected.toChars()); + break; } *type = Type::ret(expected); @@ -4461,6 +5919,26 @@ CheckCoercedMathBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSMath return CoerceResult(f, callNode, ret, actual, type); } +static bool +CheckCoercedSimdCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, + Type ret, Type* type) +{ + MOZ_ASSERT(ret.isCanonical()); + + Type actual; + if (global->isSimdCtor()) { + if (!CheckSimdCtorCall(f, call, global, &actual)) + return false; + MOZ_ASSERT(actual.isSimd()); + } else { + MOZ_ASSERT(global->isSimdOperation()); + if (!CheckSimdOperationCall(f, call, global, &actual)) + return false; + } + + return CoerceResult(f, call, ret, actual, type); +} + static bool CheckCoercedAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func, Type ret, Type* type) @@ -4513,6 +5991,9 @@ CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type) case ModuleValidator::Global::ArrayView: case ModuleValidator::Global::ArrayViewCtor: return f.failName(callee, "'%s' is not callable function", callee->name()); + case ModuleValidator::Global::SimdCtor: + case ModuleValidator::Global::SimdOp: + return CheckCoercedSimdCall(f, call, global, ret, type); case ModuleValidator::Global::Function: break; } @@ -4697,9 +6178,11 @@ CheckConditional(FunctionValidator& f, ParseNode* ternary, Type* type) *type = Type::Double; } else if (thenType.isFloat() && elseType.isFloat()) { *type = Type::Float; + } else if (thenType.isSimd() && elseType == thenType) { + *type = thenType; } else { return f.failf(ternary, "then/else branches of conditional must both produce int, float, " - "double, current types are %s and %s", + "double or SIMD types, current types are %s and %s", thenType.toChars(), elseType.toChars()); } @@ -4726,6 +6209,16 @@ IsValidIntMultiplyConstant(ModuleValidator& m, ParseNode* expr) case NumLit::Double: case NumLit::Float: case NumLit::OutOfRangeInt: + case NumLit::Int8x16: + case NumLit::Uint8x16: + case NumLit::Int16x8: + case NumLit::Uint16x8: + case NumLit::Int32x4: + case NumLit::Uint32x4: + case NumLit::Float32x4: + case NumLit::Bool8x16: + case NumLit::Bool16x8: + case NumLit::Bool32x4: return false; } @@ -5399,6 +6892,16 @@ CheckCaseExpr(FunctionValidator& f, ParseNode* caseExpr, int32_t* value) return f.fail(caseExpr, "switch case expression out of integer range"); case NumLit::Double: case NumLit::Float: + case NumLit::Int8x16: + case NumLit::Uint8x16: + case NumLit::Int16x8: + case NumLit::Uint16x8: + case NumLit::Int32x4: + case NumLit::Uint32x4: + case NumLit::Float32x4: + case NumLit::Bool8x16: + case NumLit::Bool16x8: + case NumLit::Bool32x4: return f.fail(caseExpr, "switch case expression must be an integer literal"); } @@ -6144,13 +7647,20 @@ HasObjectValueOfMethodPure(JSObject* obj, JSContext* cx) static bool HasPureCoercion(JSContext* cx, HandleValue v) { - // Ideally, we'd reject all non-primitives, but Emscripten has a bug that - // generates code that passes functions for some imports. To avoid breaking - // all the code that contains this bug, we make an exception for functions - // that don't have user-defined valueOf or toString, for their coercions - // are not observable and coercion via ToNumber/ToInt32 definitely produces - // NaN/0. We should remove this special case later once most apps have been - // built with newer Emscripten. + // Unsigned SIMD types are not allowed in function signatures. + if (IsVectorObject(v) || IsVectorObject(v) || IsVectorObject(v) || + IsVectorObject(v) || IsVectorObject(v) || + IsVectorObject(v) || IsVectorObject(v)) { + return true; + } + + // Ideally, we'd reject all non-SIMD non-primitives, but Emscripten has a + // bug that generates code that passes functions for some imports. To avoid + // breaking all the code that contains this bug, we make an exception for + // functions that don't have user-defined valueOf or toString, for their + // coercions are not observable and coercion via ToNumber/ToInt32 + // definitely produces NaN/0. We should remove this special case later once + // most apps have been built with newer Emscripten. if (v.toObject().is() && HasNoToPrimitiveMethodPure(&v.toObject(), cx) && HasObjectValueOfMethodPure(&v.toObject(), cx) && @@ -6158,6 +7668,7 @@ HasPureCoercion(JSContext* cx, HandleValue v) { return true; } + return false; } @@ -6202,6 +7713,58 @@ ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global, HandleValue imp val->emplace(d); return true; } + case ValType::I8x16: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + val->emplace(simdConstant.asInt8x16()); + return true; + } + case ValType::I16x8: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + val->emplace(simdConstant.asInt16x8()); + return true; + } + case ValType::I32x4: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + val->emplace(simdConstant.asInt32x4()); + return true; + } + case ValType::F32x4: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + val->emplace(simdConstant.asFloat32x4()); + return true; + } + case ValType::B8x16: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + // Bool8x16 uses the same data layout as Int8x16. + val->emplace(simdConstant.asInt8x16()); + return true; + } + case ValType::B16x8: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + // Bool16x8 uses the same data layout as Int16x8. + val->emplace(simdConstant.asInt16x8()); + return true; + } + case ValType::B32x4: { + SimdConstant simdConstant; + if (!ToSimdConstant(cx, v, &simdConstant)) + return false; + // Bool32x4 uses the same data layout as Int32x4. + val->emplace(simdConstant.asInt32x4()); + return true; + } case ValType::Ref: case ValType::AnyRef: { MOZ_CRASH("not available in asm.js"); @@ -6284,6 +7847,167 @@ ValidateMathBuiltinFunction(JSContext* cx, const AsmJSGlobal& global, HandleValu return true; } +static bool +ValidateSimdType(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal, + MutableHandleValue out) +{ + RootedValue v(cx); + if (!GetDataProperty(cx, globalVal, cx->names().SIMD, &v)) + return false; + + SimdType type; + if (global.which() == AsmJSGlobal::SimdCtor) + type = global.simdCtorType(); + else + type = global.simdOperationType(); + + RootedPropertyName simdTypeName(cx, SimdTypeToName(cx->names(), type)); + if (!GetDataProperty(cx, v, simdTypeName, &v)) + return false; + + if (!v.isObject()) + return LinkFail(cx, "bad SIMD type"); + + RootedObject simdDesc(cx, &v.toObject()); + if (!simdDesc->is()) + return LinkFail(cx, "bad SIMD type"); + + if (type != simdDesc->as().type()) + return LinkFail(cx, "bad SIMD type"); + + out.set(v); + return true; +} + +static bool +ValidateSimdType(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) +{ + RootedValue _(cx); + return ValidateSimdType(cx, global, globalVal, &_); +} + +static bool +ValidateSimdOperation(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) +{ + RootedValue v(cx); + MOZ_ALWAYS_TRUE(ValidateSimdType(cx, global, globalVal, &v)); + + if (!GetDataProperty(cx, v, global.field(), &v)) + return false; + + Native native = nullptr; + switch (global.simdOperationType()) { +#define SET_NATIVE_INT8X16(op) case SimdOperation::Fn_##op: native = simd_int8x16_##op; break; +#define SET_NATIVE_INT16X8(op) case SimdOperation::Fn_##op: native = simd_int16x8_##op; break; +#define SET_NATIVE_INT32X4(op) case SimdOperation::Fn_##op: native = simd_int32x4_##op; break; +#define SET_NATIVE_UINT8X16(op) case SimdOperation::Fn_##op: native = simd_uint8x16_##op; break; +#define SET_NATIVE_UINT16X8(op) case SimdOperation::Fn_##op: native = simd_uint16x8_##op; break; +#define SET_NATIVE_UINT32X4(op) case SimdOperation::Fn_##op: native = simd_uint32x4_##op; break; +#define SET_NATIVE_FLOAT32X4(op) case SimdOperation::Fn_##op: native = simd_float32x4_##op; break; +#define SET_NATIVE_BOOL8X16(op) case SimdOperation::Fn_##op: native = simd_bool8x16_##op; break; +#define SET_NATIVE_BOOL16X8(op) case SimdOperation::Fn_##op: native = simd_bool16x8_##op; break; +#define SET_NATIVE_BOOL32X4(op) case SimdOperation::Fn_##op: native = simd_bool32x4_##op; break; +#define FALLTHROUGH(op) case SimdOperation::Fn_##op: + case SimdType::Int8x16: + switch (global.simdOperation()) { + FORALL_INT8X16_ASMJS_OP(SET_NATIVE_INT8X16) + SET_NATIVE_INT8X16(fromUint8x16Bits) + SET_NATIVE_INT8X16(fromUint16x8Bits) + SET_NATIVE_INT8X16(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Int16x8: + switch (global.simdOperation()) { + FORALL_INT16X8_ASMJS_OP(SET_NATIVE_INT16X8) + SET_NATIVE_INT16X8(fromUint8x16Bits) + SET_NATIVE_INT16X8(fromUint16x8Bits) + SET_NATIVE_INT16X8(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Int32x4: + switch (global.simdOperation()) { + FORALL_INT32X4_ASMJS_OP(SET_NATIVE_INT32X4) + SET_NATIVE_INT32X4(fromUint8x16Bits) + SET_NATIVE_INT32X4(fromUint16x8Bits) + SET_NATIVE_INT32X4(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Uint8x16: + switch (global.simdOperation()) { + FORALL_INT8X16_ASMJS_OP(SET_NATIVE_UINT8X16) + SET_NATIVE_UINT8X16(fromInt8x16Bits) + SET_NATIVE_UINT8X16(fromUint16x8Bits) + SET_NATIVE_UINT8X16(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Uint16x8: + switch (global.simdOperation()) { + FORALL_INT16X8_ASMJS_OP(SET_NATIVE_UINT16X8) + SET_NATIVE_UINT16X8(fromUint8x16Bits) + SET_NATIVE_UINT16X8(fromInt16x8Bits) + SET_NATIVE_UINT16X8(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Uint32x4: + switch (global.simdOperation()) { + FORALL_INT32X4_ASMJS_OP(SET_NATIVE_UINT32X4) + SET_NATIVE_UINT32X4(fromUint8x16Bits) + SET_NATIVE_UINT32X4(fromUint16x8Bits) + SET_NATIVE_UINT32X4(fromInt32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Float32x4: + switch (global.simdOperation()) { + FORALL_FLOAT32X4_ASMJS_OP(SET_NATIVE_FLOAT32X4) + SET_NATIVE_FLOAT32X4(fromUint8x16Bits) + SET_NATIVE_FLOAT32X4(fromUint16x8Bits) + SET_NATIVE_FLOAT32X4(fromUint32x4Bits) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Bool8x16: + switch (global.simdOperation()) { + FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL8X16) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Bool16x8: + switch (global.simdOperation()) { + FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL16X8) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + case SimdType::Bool32x4: + switch (global.simdOperation()) { + FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL32X4) + default: MOZ_CRASH("shouldn't have been validated in the first place"); + } + break; + default: MOZ_CRASH("unhandled simd type"); +#undef FALLTHROUGH +#undef SET_NATIVE_INT8X16 +#undef SET_NATIVE_INT16X8 +#undef SET_NATIVE_INT32X4 +#undef SET_NATIVE_UINT8X16 +#undef SET_NATIVE_UINT16X8 +#undef SET_NATIVE_UINT32X4 +#undef SET_NATIVE_FLOAT32X4 +#undef SET_NATIVE_BOOL8X16 +#undef SET_NATIVE_BOOL16X8 +#undef SET_NATIVE_BOOL32X4 +#undef SET_NATIVE + } + if (!native || !IsNativeFunction(v, native)) + return LinkFail(cx, "bad SIMD.type.* operation"); + return true; +} + static bool ValidateAtomicsBuiltinFunction(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { @@ -6386,11 +8110,12 @@ CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata, HandleValue bufferVal, // On 64-bit, bounds checks are statically removed so the huge guard // region is always necessary. On 32-bit, allocating a guard page // requires reallocating the incoming ArrayBuffer which could trigger - // OOM. Thus, don't ask for a guard page in this case; + // OOM. Thus, only ask for a guard page when SIMD is used since SIMD + // allows unaligned memory access (see MaxMemoryAccessSize comment); #ifdef WASM_HUGE_MEMORY bool needGuard = true; #else - bool needGuard = false; + bool needGuard = metadata.usesSimd; #endif Rooted arrayBuffer(cx, &buffer->as()); if (!ArrayBufferObject::prepareForAsmJS(cx, arrayBuffer, needGuard)) @@ -6453,6 +8178,14 @@ GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal, if (!ValidateConstant(cx, global, globalVal)) return false; break; + case AsmJSGlobal::SimdCtor: + if (!ValidateSimdType(cx, global, globalVal)) + return false; + break; + case AsmJSGlobal::SimdOp: + if (!ValidateSimdOperation(cx, global, globalVal)) + return false; + break; } } diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 72c745de0fcf..34cb5b22fdd4 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -9050,7 +9050,7 @@ BaseCompiler::emitAtomicCmpXchg(ValType type, Scalar::Type viewType) return true; MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); if (Scalar::byteSize(viewType) <= 4) { PopAtomicCmpXchg32Regs regs(this, type, viewType); @@ -9106,7 +9106,7 @@ BaseCompiler::emitAtomicLoad(ValType type, Scalar::Type viewType) return true; MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset(), - Synchronization::Load()); + /*numSimdElems=*/ 0, Synchronization::Load()); if (Scalar::byteSize(viewType) <= sizeof(void*)) return loadCommon(&access, type); @@ -9150,7 +9150,7 @@ BaseCompiler::emitAtomicRMW(ValType type, Scalar::Type viewType, AtomicOp op) return true; MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset(), - Synchronization::Full()); + /*numSimdElems=*/ 0, Synchronization::Full()); if (Scalar::byteSize(viewType) <= 4) { PopAtomicRMW32Regs regs(this, type, viewType, op); @@ -9213,7 +9213,7 @@ BaseCompiler::emitAtomicStore(ValType type, Scalar::Type viewType) return true; MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset(), - Synchronization::Store()); + /*numSimdElems=*/ 0, Synchronization::Store()); if (Scalar::byteSize(viewType) <= sizeof(void*)) return storeCommon(&access, type); @@ -9241,7 +9241,7 @@ BaseCompiler::emitAtomicXchg(ValType type, Scalar::Type viewType) AccessCheck check; MemoryAccessDesc access(viewType, addr.align, addr.offset, bytecodeOffset(), - Synchronization::Full()); + /*numSimdElems=*/ 0, Synchronization::Full()); if (Scalar::byteSize(viewType) <= 4) { PopAtomicXchg32Regs regs(this, type, viewType); diff --git a/js/src/wasm/WasmBaselineCompile.h b/js/src/wasm/WasmBaselineCompile.h index 0722bba74897..db8ae7f42a94 100644 --- a/js/src/wasm/WasmBaselineCompile.h +++ b/js/src/wasm/WasmBaselineCompile.h @@ -25,7 +25,7 @@ namespace js { namespace wasm { // Return whether BaselineCompileFunction can generate code on the current device. -// Note: asm.js is also currently not supported due to Atomics. +// Note: asm.js is also currently not supported due to Atomics and SIMD. bool BaselineCanCompile(); diff --git a/js/src/wasm/WasmBinaryConstants.h b/js/src/wasm/WasmBinaryConstants.h index 6c8ac2bf6e57..543dd7961b35 100644 --- a/js/src/wasm/WasmBinaryConstants.h +++ b/js/src/wasm/WasmBinaryConstants.h @@ -19,6 +19,8 @@ #ifndef wasm_binary_h #define wasm_binary_h +#include "builtin/SIMDConstants.h" + namespace js { namespace wasm { @@ -48,6 +50,15 @@ enum class TypeCode F32 = 0x7d, // SLEB128(-0x03) F64 = 0x7c, // SLEB128(-0x04) + // Only emitted internally for asm.js, likely to get collapsed into I128 + I8x16 = 0x7b, + I16x8 = 0x7a, + I32x4 = 0x79, + F32x4 = 0x78, + B8x16 = 0x77, + B16x8 = 0x76, + B32x4 = 0x75, + // A function pointer with any signature AnyFunc = 0x70, // SLEB128(-0x10) @@ -483,6 +494,82 @@ enum class MozOp I32AtomicsStore, I32AtomicsBinOp, + // SIMD +#define SIMD_OPCODE(TYPE, OP) TYPE##OP, +#define _(OP) SIMD_OPCODE(I8x16, OP) + FORALL_INT8X16_ASMJS_OP(_) + I8x16Constructor, + I8x16Const, +#undef _ + // Unsigned I8x16 operations. These are the SIMD.Uint8x16 operations that + // behave differently from their SIMD.Int8x16 counterparts. + I8x16extractLaneU, + I8x16addSaturateU, + I8x16subSaturateU, + I8x16shiftRightByScalarU, + I8x16lessThanU, + I8x16lessThanOrEqualU, + I8x16greaterThanU, + I8x16greaterThanOrEqualU, + +#define SIMD_OPCODE(TYPE, OP) TYPE##OP, +#define _(OP) SIMD_OPCODE(I16x8, OP) + FORALL_INT16X8_ASMJS_OP(_) + I16x8Constructor, + I16x8Const, +#undef _ + // Unsigned I16x8 operations. These are the SIMD.Uint16x8 operations that + // behave differently from their SIMD.Int16x8 counterparts. + I16x8extractLaneU, + I16x8addSaturateU, + I16x8subSaturateU, + I16x8shiftRightByScalarU, + I16x8lessThanU, + I16x8lessThanOrEqualU, + I16x8greaterThanU, + I16x8greaterThanOrEqualU, + +#define SIMD_OPCODE(TYPE, OP) TYPE##OP, +#define _(OP) SIMD_OPCODE(I32x4, OP) + FORALL_INT32X4_ASMJS_OP(_) + I32x4Constructor, + I32x4Const, +#undef _ + // Unsigned I32x4 operations. These are the SIMD.Uint32x4 operations that + // behave differently from their SIMD.Int32x4 counterparts. + I32x4shiftRightByScalarU, + I32x4lessThanU, + I32x4lessThanOrEqualU, + I32x4greaterThanU, + I32x4greaterThanOrEqualU, + I32x4fromFloat32x4U, +#define _(OP) SIMD_OPCODE(F32x4, OP) + FORALL_FLOAT32X4_ASMJS_OP(_) + F32x4Constructor, + F32x4Const, +#undef _ + +#define _(OP) SIMD_OPCODE(B8x16, OP) + FORALL_BOOL_SIMD_OP(_) + B8x16Constructor, + B8x16Const, +#undef _ +#undef OPCODE + +#define _(OP) SIMD_OPCODE(B16x8, OP) + FORALL_BOOL_SIMD_OP(_) + B16x8Constructor, + B16x8Const, +#undef _ +#undef OPCODE + +#define _(OP) SIMD_OPCODE(B32x4, OP) + FORALL_BOOL_SIMD_OP(_) + B32x4Constructor, + B32x4Const, +#undef _ +#undef OPCODE + Limit }; diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp index 5358fbae64ee..a56615549cee 100644 --- a/js/src/wasm/WasmBuiltins.cpp +++ b/js/src/wasm/WasmBuiltins.cpp @@ -261,6 +261,8 @@ WasmHandleTrap() return ReportError(cx, JSMSG_WASM_IND_CALL_TO_NULL); case Trap::IndirectCallBadSig: return ReportError(cx, JSMSG_WASM_IND_CALL_BAD_SIG); + case Trap::ImpreciseSimdConversion: + return ReportError(cx, JSMSG_SIMD_FAILED_CONVERSION); case Trap::OutOfBounds: return ReportError(cx, JSMSG_WASM_OUT_OF_BOUNDS); case Trap::UnalignedAccess: diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index e65650763147..53bbcd895b7d 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -18,6 +18,7 @@ #include "wasm/WasmInstance.h" +#include "builtin/SIMD.h" #include "jit/AtomicOperations.h" #include "jit/BaselineJIT.h" #include "jit/InlinableNatives.h" @@ -143,6 +144,13 @@ Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, con break; } case ValType::I64: + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: MOZ_CRASH("unhandled type in callImport"); } } @@ -208,6 +216,13 @@ Instance::callImport(JSContext* cx, uint32_t funcImportIndex, unsigned argc, con case ValType::Ref: MOZ_CRASH("case guarded above"); case ValType::AnyRef: MOZ_CRASH("case guarded above"); case ValType::I64: MOZ_CRASH("NYI"); + case ValType::I8x16: MOZ_CRASH("NYI"); + case ValType::I16x8: MOZ_CRASH("NYI"); + case ValType::I32x4: MOZ_CRASH("NYI"); + case ValType::F32x4: MOZ_CRASH("NYI"); + case ValType::B8x16: MOZ_CRASH("NYI"); + case ValType::B16x8: MOZ_CRASH("NYI"); + case ValType::B32x4: MOZ_CRASH("NYI"); } if (!TypeScript::ArgTypes(script, i)->hasType(type)) return true; @@ -795,12 +810,12 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args) // The calling convention for an external call into wasm is to pass an // array of 16-byte values where each value contains either a coerced int32 - // (in the low word), or a double value (in the low dword) value, with the - // coercions specified by the wasm signature. The external entry point - // unpacks this array into the system-ABI-specified registers and stack - // memory and then calls into the internal entry point. The return value is - // stored in the first element of the array (which, therefore, must have - // length >= 1). + // (in the low word), a double value (in the low dword) or a SIMD vector + // value, with the coercions specified by the wasm signature. The external + // entry point unpacks this array into the system-ABI-specified registers + // and stack memory and then calls into the internal entry point. The return + // value is stored in the first element of the array (which, therefore, must + // have length >= 1). Vector exportArgs(cx); if (!exportArgs.resize(Max(1, func.funcType().args().length()))) return false; @@ -829,6 +844,58 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args) return false; break; } + case ValType::I8x16: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + memcpy(&exportArgs[i], simd.asInt8x16(), Simd128DataSize); + break; + } + case ValType::I16x8: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + memcpy(&exportArgs[i], simd.asInt16x8(), Simd128DataSize); + break; + } + case ValType::I32x4: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + memcpy(&exportArgs[i], simd.asInt32x4(), Simd128DataSize); + break; + } + case ValType::F32x4: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + memcpy(&exportArgs[i], simd.asFloat32x4(), Simd128DataSize); + break; + } + case ValType::B8x16: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + // Bool8x16 uses the same representation as Int8x16. + memcpy(&exportArgs[i], simd.asInt8x16(), Simd128DataSize); + break; + } + case ValType::B16x8: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + // Bool16x8 uses the same representation as Int16x8. + memcpy(&exportArgs[i], simd.asInt16x8(), Simd128DataSize); + break; + } + case ValType::B32x4: { + SimdConstant simd; + if (!ToSimdConstant(cx, v, &simd)) + return false; + // Bool32x4 uses the same representation as Int32x4. + memcpy(&exportArgs[i], simd.asInt32x4(), Simd128DataSize); + break; + } } } @@ -883,6 +950,41 @@ Instance::callExport(JSContext* cx, uint32_t funcIndex, CallArgs args) retObj = *(JSObject**)retAddr; expectsObject = true; break; + case ExprType::I8x16: + retObj = CreateSimd(cx, (int8_t*)retAddr); + if (!retObj) + return false; + break; + case ExprType::I16x8: + retObj = CreateSimd(cx, (int16_t*)retAddr); + if (!retObj) + return false; + break; + case ExprType::I32x4: + retObj = CreateSimd(cx, (int32_t*)retAddr); + if (!retObj) + return false; + break; + case ExprType::F32x4: + retObj = CreateSimd(cx, (float*)retAddr); + if (!retObj) + return false; + break; + case ExprType::B8x16: + retObj = CreateSimd(cx, (int8_t*)retAddr); + if (!retObj) + return false; + break; + case ExprType::B16x8: + retObj = CreateSimd(cx, (int16_t*)retAddr); + if (!retObj) + return false; + break; + case ExprType::B32x4: + retObj = CreateSimd(cx, (int32_t*)retAddr); + if (!retObj) + return false; + break; case ExprType::Limit: MOZ_CRASH("Limit"); } diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp index 45581ac6e48a..efd02ada5bf4 100644 --- a/js/src/wasm/WasmIonCompile.cpp +++ b/js/src/wasm/WasmIonCompile.cpp @@ -223,6 +223,30 @@ class FunctionCompiler case ValType::AnyRef: MOZ_CRASH("ion support for ref/anyref value NYI"); break; + case ValType::I8x16: + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX16(0), MIRType::Int8x16); + break; + case ValType::I16x8: + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX8(0), MIRType::Int16x8); + break; + case ValType::I32x4: + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType::Int32x4); + break; + case ValType::F32x4: + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0.f), MIRType::Float32x4); + break; + case ValType::B8x16: + // Bool8x16 uses the same data layout as Int8x16. + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX16(0), MIRType::Bool8x16); + break; + case ValType::B16x8: + // Bool16x8 uses the same data layout as Int16x8. + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX8(0), MIRType::Bool16x8); + break; + case ValType::B32x4: + // Bool32x4 uses the same data layout as Int32x4. + ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType::Bool32x4); + break; } curBlock_->add(ins); @@ -267,6 +291,16 @@ class FunctionCompiler /***************************** Code generation (after local scope setup) */ + MDefinition* constant(const SimdConstant& v, MIRType type) + { + if (inDeadCode()) + return nullptr; + MInstruction* constant; + constant = MSimdConstant::New(alloc(), v, type); + curBlock_->add(constant); + return constant; + } + MDefinition* constant(const Value& v, MIRType type) { if (inDeadCode()) @@ -369,6 +403,172 @@ class FunctionCompiler return ins; } + MDefinition* unarySimd(MDefinition* input, MSimdUnaryArith::Operation op, MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(input->type()) && input->type() == type); + MInstruction* ins = MSimdUnaryArith::New(alloc(), input, op); + curBlock_->add(ins); + return ins; + } + + MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryArith::Operation op, + MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); + MOZ_ASSERT(lhs->type() == type); + return MSimdBinaryArith::AddLegalized(alloc(), curBlock_, lhs, rhs, op); + } + + MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryBitwise::Operation op, + MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); + MOZ_ASSERT(lhs->type() == type); + auto* ins = MSimdBinaryBitwise::New(alloc(), lhs, rhs, op); + curBlock_->add(ins); + return ins; + } + + MDefinition* binarySimdComp(MDefinition* lhs, MDefinition* rhs, MSimdBinaryComp::Operation op, + SimdSign sign) + { + if (inDeadCode()) + return nullptr; + + return MSimdBinaryComp::AddLegalized(alloc(), curBlock_, lhs, rhs, op, sign); + } + + MDefinition* binarySimdSaturating(MDefinition* lhs, MDefinition* rhs, + MSimdBinarySaturating::Operation op, SimdSign sign) + { + if (inDeadCode()) + return nullptr; + + auto* ins = MSimdBinarySaturating::New(alloc(), lhs, rhs, op, sign); + curBlock_->add(ins); + return ins; + } + + MDefinition* binarySimdShift(MDefinition* lhs, MDefinition* rhs, MSimdShift::Operation op) + { + if (inDeadCode()) + return nullptr; + + return MSimdShift::AddLegalized(alloc(), curBlock_, lhs, rhs, op); + } + + MDefinition* swizzleSimd(MDefinition* vector, const uint8_t lanes[], MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(vector->type() == type); + MSimdSwizzle* ins = MSimdSwizzle::New(alloc(), vector, lanes); + curBlock_->add(ins); + return ins; + } + + MDefinition* shuffleSimd(MDefinition* lhs, MDefinition* rhs, const uint8_t lanes[], + MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(lhs->type() == type); + MInstruction* ins = MSimdShuffle::New(alloc(), lhs, rhs, lanes); + curBlock_->add(ins); + return ins; + } + + MDefinition* insertElementSimd(MDefinition* vec, MDefinition* val, unsigned lane, MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(vec->type()) && vec->type() == type); + MOZ_ASSERT(SimdTypeToLaneArgumentType(vec->type()) == val->type()); + MSimdInsertElement* ins = MSimdInsertElement::New(alloc(), vec, val, lane); + curBlock_->add(ins); + return ins; + } + + MDefinition* selectSimd(MDefinition* mask, MDefinition* lhs, MDefinition* rhs, MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(mask->type())); + MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); + MOZ_ASSERT(lhs->type() == type); + MSimdSelect* ins = MSimdSelect::New(alloc(), mask, lhs, rhs); + curBlock_->add(ins); + return ins; + } + + MDefinition* simdAllTrue(MDefinition* boolVector) + { + if (inDeadCode()) + return nullptr; + + MSimdAllTrue* ins = MSimdAllTrue::New(alloc(), boolVector, MIRType::Int32); + curBlock_->add(ins); + return ins; + } + + MDefinition* simdAnyTrue(MDefinition* boolVector) + { + if (inDeadCode()) + return nullptr; + + MSimdAnyTrue* ins = MSimdAnyTrue::New(alloc(), boolVector, MIRType::Int32); + curBlock_->add(ins); + return ins; + } + + // fromXXXBits() + MDefinition* bitcastSimd(MDefinition* vec, MIRType from, MIRType to) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(vec->type() == from); + MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to); + auto* ins = MSimdReinterpretCast::New(alloc(), vec, to); + curBlock_->add(ins); + return ins; + } + + // Int <--> Float conversions. + MDefinition* convertSimd(MDefinition* vec, MIRType from, MIRType to, SimdSign sign) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to); + return MSimdConvert::AddLegalized(alloc(), curBlock_, vec, to, sign, bytecodeOffset()); + } + + MDefinition* splatSimd(MDefinition* v, MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(type)); + MOZ_ASSERT(SimdTypeToLaneArgumentType(type) == v->type()); + MSimdSplat* ins = MSimdSplat::New(alloc(), v, type); + curBlock_->add(ins); + return ins; + } + MDefinition* minMax(MDefinition* lhs, MDefinition* rhs, MIRType type, bool isMax) { if (inDeadCode()) return nullptr; @@ -691,7 +891,7 @@ class FunctionCompiler MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); MInstruction* load = nullptr; - if (env_.isAsmJS() && !access->isAtomic()) { + if (env_.isAsmJS() && !access->isAtomic() && !access->isSimd()) { MOZ_ASSERT(access->offset() == 0); MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit(); load = MAsmJSLoadHeap::New(alloc(), memoryBase, base, boundsCheckLimit, access->type()); @@ -712,7 +912,7 @@ class FunctionCompiler MWasmLoadTls* memoryBase = maybeLoadMemoryBase(); MInstruction* store = nullptr; - if (env_.isAsmJS() && !access->isAtomic()) { + if (env_.isAsmJS() && !access->isAtomic() && !access->isSimd()) { MOZ_ASSERT(access->offset() == 0); MWasmLoadTls* boundsCheckLimit = maybeLoadBoundsCheckLimit(); store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit, @@ -843,6 +1043,31 @@ class FunctionCompiler curBlock_->add(MWasmInterruptCheck::New(alloc(), tlsPointer_, bytecodeOffset())); } + MDefinition* extractSimdElement(unsigned lane, MDefinition* base, MIRType type, SimdSign sign) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(base->type())); + MOZ_ASSERT(!IsSimdType(type)); + auto* ins = MSimdExtractElement::New(alloc(), base, type, lane, sign); + curBlock_->add(ins); + return ins; + } + + template + MDefinition* constructSimd(MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w, + MIRType type) + { + if (inDeadCode()) + return nullptr; + + MOZ_ASSERT(IsSimdType(type)); + T* ins = T::New(alloc(), type, x, y, z, w); + curBlock_->add(ins); + return ins; + } + /***************************************************************** Calls */ // The IonMonkey backend maintains a single stack offset (from the stack @@ -1657,6 +1882,83 @@ EmitF64Const(FunctionCompiler& f) return true; } +static bool +EmitI8x16Const(FunctionCompiler& f) +{ + I8x16 i8x16; + if (!f.iter().readI8x16Const(&i8x16)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX16(i8x16), MIRType::Int8x16)); + return true; +} + +static bool +EmitI16x8Const(FunctionCompiler& f) +{ + I16x8 i16x8; + if (!f.iter().readI16x8Const(&i16x8)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX8(i16x8), MIRType::Int16x8)); + return true; +} + +static bool +EmitI32x4Const(FunctionCompiler& f) +{ + I32x4 i32x4; + if (!f.iter().readI32x4Const(&i32x4)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX4(i32x4), MIRType::Int32x4)); + return true; +} + +static bool +EmitF32x4Const(FunctionCompiler& f) +{ + F32x4 f32x4; + if (!f.iter().readF32x4Const(&f32x4)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX4(f32x4), MIRType::Float32x4)); + return true; +} + +static bool +EmitB8x16Const(FunctionCompiler& f) +{ + I8x16 i8x16; + if (!f.iter().readB8x16Const(&i8x16)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX16(i8x16), MIRType::Bool8x16)); + return true; +} + +static bool +EmitB16x8Const(FunctionCompiler& f) +{ + I16x8 i16x8; + if (!f.iter().readB16x8Const(&i16x8)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX8(i16x8), MIRType::Bool16x8)); + return true; +} + +static bool +EmitB32x4Const(FunctionCompiler& f) +{ + I32x4 i32x4; + if (!f.iter().readB32x4Const(&i32x4)) + return false; + + f.iter().setResult(f.constant(SimdConstant::CreateX4(i32x4), MIRType::Bool32x4)); + return true; +} + static bool EmitBlock(FunctionCompiler& f) { @@ -2018,6 +2320,18 @@ EmitGetGlobal(FunctionCompiler& f) case ValType::F64: result = f.constant(value.f64()); break; + case ValType::I8x16: + result = f.constant(SimdConstant::CreateX16(value.i8x16()), mirType); + break; + case ValType::I16x8: + result = f.constant(SimdConstant::CreateX8(value.i16x8()), mirType); + break; + case ValType::I32x4: + result = f.constant(SimdConstant::CreateX4(value.i32x4()), mirType); + break; + case ValType::F32x4: + result = f.constant(SimdConstant::CreateX4(value.f32x4()), mirType); + break; default: MOZ_CRASH("unexpected type in EmitGetGlobal"); } @@ -2479,7 +2793,7 @@ EmitOldAtomicsLoad(FunctionCompiler& f) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Load()); + /*numSimdExprs=*/ 0, Synchronization::Load()); auto* ins = f.load(addr.base, &access, ValType::I32); if (!f.inDeadCode() && !ins) @@ -2499,7 +2813,7 @@ EmitOldAtomicsStore(FunctionCompiler& f) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Store()); + /*numSimdExprs=*/ 0, Synchronization::Store()); f.store(addr.base, &access, value); f.iter().setResult(value); @@ -2517,7 +2831,7 @@ EmitOldAtomicsBinOp(FunctionCompiler& f) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); auto* ins = f.atomicBinopHeap(op, addr.base, &access, ValType::I32, value); if (!f.inDeadCode() && !ins) @@ -2538,7 +2852,7 @@ EmitOldAtomicsCompareExchange(FunctionCompiler& f) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); auto* ins = f.atomicCompareExchangeHeap(addr.base, &access, ValType::I32, oldValue, newValue); if (!f.inDeadCode() && !ins) @@ -2558,7 +2872,7 @@ EmitOldAtomicsExchange(FunctionCompiler& f) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); auto* ins = f.atomicExchangeHeap(addr.base, &access, ValType::I32, value); if (!f.inDeadCode() && !ins) @@ -2568,6 +2882,489 @@ EmitOldAtomicsExchange(FunctionCompiler& f) return true; } +static bool +EmitSimdUnary(FunctionCompiler& f, ValType type, SimdOperation simdOp) +{ + MSimdUnaryArith::Operation op; + switch (simdOp) { + case SimdOperation::Fn_abs: + op = MSimdUnaryArith::abs; + break; + case SimdOperation::Fn_neg: + op = MSimdUnaryArith::neg; + break; + case SimdOperation::Fn_not: + op = MSimdUnaryArith::not_; + break; + case SimdOperation::Fn_sqrt: + op = MSimdUnaryArith::sqrt; + break; + case SimdOperation::Fn_reciprocalApproximation: + op = MSimdUnaryArith::reciprocalApproximation; + break; + case SimdOperation::Fn_reciprocalSqrtApproximation: + op = MSimdUnaryArith::reciprocalSqrtApproximation; + break; + default: + MOZ_CRASH("not a simd unary arithmetic operation"); + } + + MDefinition* input; + if (!f.iter().readUnary(type, &input)) + return false; + + f.iter().setResult(f.unarySimd(input, op, ToMIRType(type))); + return true; +} + +template +inline bool +EmitSimdBinary(FunctionCompiler& f, ValType type, OpKind op) +{ + MDefinition* lhs; + MDefinition* rhs; + if (!f.iter().readBinary(type, &lhs, &rhs)) + return false; + + f.iter().setResult(f.binarySimd(lhs, rhs, op, ToMIRType(type))); + return true; +} + +static bool +EmitSimdBinaryComp(FunctionCompiler& f, ValType operandType, MSimdBinaryComp::Operation op, + SimdSign sign) +{ + MDefinition* lhs; + MDefinition* rhs; + if (!f.iter().readSimdComparison(operandType, &lhs, &rhs)) + return false; + + f.iter().setResult(f.binarySimdComp(lhs, rhs, op, sign)); + return true; +} + +static bool +EmitSimdBinarySaturating(FunctionCompiler& f, ValType type, MSimdBinarySaturating::Operation op, + SimdSign sign) +{ + MDefinition* lhs; + MDefinition* rhs; + if (!f.iter().readBinary(type, &lhs, &rhs)) + return false; + + f.iter().setResult(f.binarySimdSaturating(lhs, rhs, op, sign)); + return true; +} + +static bool +EmitSimdShift(FunctionCompiler& f, ValType operandType, MSimdShift::Operation op) +{ + MDefinition* lhs; + MDefinition* rhs; + if (!f.iter().readSimdShiftByScalar(operandType, &lhs, &rhs)) + return false; + + f.iter().setResult(f.binarySimdShift(lhs, rhs, op)); + return true; +} + +static ValType +SimdToLaneType(ValType type) +{ + switch (type.code()) { + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: return ValType::I32; + case ValType::F32x4: return ValType::F32; + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: return ValType::I32; // Boolean lanes are Int32 in asm. + case ValType::I32: + case ValType::I64: + case ValType::F32: + case ValType::F64: + case ValType::Ref: + case ValType::AnyRef: + break; + } + MOZ_CRASH("bad simd type"); +} + +static bool +EmitExtractLane(FunctionCompiler& f, ValType operandType, SimdSign sign) +{ + uint8_t lane; + MDefinition* vector; + if (!f.iter().readExtractLane(operandType, &lane, &vector)) + return false; + + f.iter().setResult(f.extractSimdElement(lane, vector, + ToMIRType(SimdToLaneType(operandType)), sign)); + return true; +} + +// Emit an I32 expression and then convert it to a boolean SIMD lane value, i.e. -1 or 0. +static MDefinition* +EmitSimdBooleanLaneExpr(FunctionCompiler& f, MDefinition* i32) +{ + // Compute !i32 - 1 to force the value range into {0, -1}. + MDefinition* noti32 = f.unary(i32); + return f.binary(noti32, f.constant(Int32Value(1), MIRType::Int32), MIRType::Int32); +} + +static bool +EmitSimdReplaceLane(FunctionCompiler& f, ValType simdType) +{ + if (IsSimdBoolType(simdType)) + f.iter().setResult(EmitSimdBooleanLaneExpr(f, f.iter().getResult())); + + uint8_t lane; + MDefinition* vector; + MDefinition* scalar; + if (!f.iter().readReplaceLane(simdType, &lane, &vector, &scalar)) + return false; + + f.iter().setResult(f.insertElementSimd(vector, scalar, lane, ToMIRType(simdType))); + return true; +} + +inline bool +EmitSimdBitcast(FunctionCompiler& f, ValType fromType, ValType toType) +{ + MDefinition* input; + if (!f.iter().readConversion(fromType, toType, &input)) + return false; + + f.iter().setResult(f.bitcastSimd(input, ToMIRType(fromType), ToMIRType(toType))); + return true; +} + +inline bool +EmitSimdConvert(FunctionCompiler& f, ValType fromType, ValType toType, SimdSign sign) +{ + MDefinition* input; + if (!f.iter().readConversion(fromType, toType, &input)) + return false; + + f.iter().setResult(f.convertSimd(input, ToMIRType(fromType), ToMIRType(toType), sign)); + return true; +} + +static bool +EmitSimdSwizzle(FunctionCompiler& f, ValType simdType) +{ + uint8_t lanes[16]; + MDefinition* vector; + if (!f.iter().readSwizzle(simdType, &lanes, &vector)) + return false; + + f.iter().setResult(f.swizzleSimd(vector, lanes, ToMIRType(simdType))); + return true; +} + +static bool +EmitSimdShuffle(FunctionCompiler& f, ValType simdType) +{ + uint8_t lanes[16]; + MDefinition* lhs; + MDefinition* rhs; + if (!f.iter().readShuffle(simdType, &lanes, &lhs, &rhs)) + return false; + + f.iter().setResult(f.shuffleSimd(lhs, rhs, lanes, ToMIRType(simdType))); + return true; +} + +static inline Scalar::Type +SimdExprTypeToViewType(ValType type, unsigned* defaultNumElems) +{ + switch (type.code()) { + case ValType::I8x16: *defaultNumElems = 16; return Scalar::Int8x16; + case ValType::I16x8: *defaultNumElems = 8; return Scalar::Int16x8; + case ValType::I32x4: *defaultNumElems = 4; return Scalar::Int32x4; + case ValType::F32x4: *defaultNumElems = 4; return Scalar::Float32x4; + default: break; + } + MOZ_CRASH("type not handled in SimdExprTypeToViewType"); +} + +static bool +EmitSimdLoad(FunctionCompiler& f, ValType resultType, unsigned numElems) +{ + unsigned defaultNumElems; + Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems); + + if (!numElems) + numElems = defaultNumElems; + + LinearMemoryAddress addr; + if (!f.iter().readLoad(resultType, Scalar::byteSize(viewType), &addr)) + return false; + + MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), numElems); + + auto* ins = f.load(addr.base, &access, resultType); + if (!f.inDeadCode() && !ins) + return false; + + f.iter().setResult(ins); + return true; +} + +static bool +EmitSimdStore(FunctionCompiler& f, ValType resultType, unsigned numElems) +{ + unsigned defaultNumElems; + Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems); + + if (!numElems) + numElems = defaultNumElems; + + LinearMemoryAddress addr; + MDefinition* value; + if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) + return false; + + MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), numElems); + + f.store(addr.base, &access, value); + return true; +} + +static bool +EmitSimdSelect(FunctionCompiler& f, ValType simdType) +{ + MDefinition* trueValue; + MDefinition* falseValue; + MDefinition* condition; + if (!f.iter().readSimdSelect(simdType, &trueValue, &falseValue, &condition)) + return false; + + f.iter().setResult(f.selectSimd(condition, trueValue, falseValue, + ToMIRType(simdType))); + return true; +} + +static bool +EmitSimdAllTrue(FunctionCompiler& f, ValType operandType) +{ + MDefinition* input; + if (!f.iter().readSimdBooleanReduction(operandType, &input)) + return false; + + f.iter().setResult(f.simdAllTrue(input)); + return true; +} + +static bool +EmitSimdAnyTrue(FunctionCompiler& f, ValType operandType) +{ + MDefinition* input; + if (!f.iter().readSimdBooleanReduction(operandType, &input)) + return false; + + f.iter().setResult(f.simdAnyTrue(input)); + return true; +} + +static bool +EmitSimdSplat(FunctionCompiler& f, ValType simdType) +{ + if (IsSimdBoolType(simdType)) + f.iter().setResult(EmitSimdBooleanLaneExpr(f, f.iter().getResult())); + + MDefinition* input; + if (!f.iter().readSplat(simdType, &input)) + return false; + + f.iter().setResult(f.splatSimd(input, ToMIRType(simdType))); + return true; +} + +// Build a SIMD vector by inserting lanes one at a time into an initial constant. +static bool +EmitSimdChainedCtor(FunctionCompiler& f, ValType valType, MIRType type, const SimdConstant& init) +{ + const unsigned length = SimdTypeToLength(type); + + DefVector args; + if (!f.iter().readSimdCtor(ValType::I32, length, valType, &args)) + return false; + + MDefinition* val = f.constant(init, type); + for (unsigned i = 0; i < length; i++) + val = f.insertElementSimd(val, args[i], i, type); + + f.iter().setResult(val); + return true; +} + +// Build a boolean SIMD vector by inserting lanes one at a time into an initial constant. +static bool +EmitSimdBooleanChainedCtor(FunctionCompiler& f, ValType valType, MIRType type, + const SimdConstant& init) +{ + const unsigned length = SimdTypeToLength(type); + + DefVector args; + if (!f.iter().readSimdCtor(ValType::I32, length, valType, &args)) + return false; + + MDefinition* val = f.constant(init, type); + for (unsigned i = 0; i < length; i++) + val = f.insertElementSimd(val, EmitSimdBooleanLaneExpr(f, args[i]), i, type); + + f.iter().setResult(val); + return true; +} + +static bool +EmitSimdCtor(FunctionCompiler& f, ValType type) +{ + switch (type.code()) { + case ValType::I8x16: + return EmitSimdChainedCtor(f, type, MIRType::Int8x16, SimdConstant::SplatX16(0)); + case ValType::I16x8: + return EmitSimdChainedCtor(f, type, MIRType::Int16x8, SimdConstant::SplatX8(0)); + case ValType::I32x4: { + DefVector args; + if (!f.iter().readSimdCtor(ValType::I32, 4, type, &args)) + return false; + + f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], + MIRType::Int32x4)); + return true; + } + case ValType::F32x4: { + DefVector args; + if (!f.iter().readSimdCtor(ValType::F32, 4, type, &args)) + return false; + + f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], + MIRType::Float32x4)); + return true; + } + case ValType::B8x16: + return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool8x16, SimdConstant::SplatX16(0)); + case ValType::B16x8: + return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool16x8, SimdConstant::SplatX8(0)); + case ValType::B32x4: { + DefVector args; + if (!f.iter().readSimdCtor(ValType::I32, 4, type, &args)) + return false; + + MOZ_ASSERT(args.length() == 4); + for (unsigned i = 0; i < 4; i++) + args[i] = EmitSimdBooleanLaneExpr(f, args[i]); + + f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], + MIRType::Bool32x4)); + return true; + } + case ValType::I32: + case ValType::I64: + case ValType::F32: + case ValType::F64: + case ValType::Ref: + case ValType::AnyRef: + break; + } + MOZ_CRASH("unexpected SIMD type"); +} + +static bool +EmitSimdOp(FunctionCompiler& f, ValType type, SimdOperation op, SimdSign sign) +{ + switch (op) { + case SimdOperation::Constructor: + return EmitSimdCtor(f, type); + case SimdOperation::Fn_extractLane: + return EmitExtractLane(f, type, sign); + case SimdOperation::Fn_replaceLane: + return EmitSimdReplaceLane(f, type); + case SimdOperation::Fn_check: + MOZ_CRASH("only used in asm.js' type system"); + case SimdOperation::Fn_splat: + return EmitSimdSplat(f, type); + case SimdOperation::Fn_select: + return EmitSimdSelect(f, type); + case SimdOperation::Fn_swizzle: + return EmitSimdSwizzle(f, type); + case SimdOperation::Fn_shuffle: + return EmitSimdShuffle(f, type); + case SimdOperation::Fn_load: + return EmitSimdLoad(f, type, 0); + case SimdOperation::Fn_load1: + return EmitSimdLoad(f, type, 1); + case SimdOperation::Fn_load2: + return EmitSimdLoad(f, type, 2); + case SimdOperation::Fn_store: + return EmitSimdStore(f, type, 0); + case SimdOperation::Fn_store1: + return EmitSimdStore(f, type, 1); + case SimdOperation::Fn_store2: + return EmitSimdStore(f, type, 2); + case SimdOperation::Fn_allTrue: + return EmitSimdAllTrue(f, type); + case SimdOperation::Fn_anyTrue: + return EmitSimdAnyTrue(f, type); + case SimdOperation::Fn_abs: + case SimdOperation::Fn_neg: + case SimdOperation::Fn_not: + case SimdOperation::Fn_sqrt: + case SimdOperation::Fn_reciprocalApproximation: + case SimdOperation::Fn_reciprocalSqrtApproximation: + return EmitSimdUnary(f, type, op); + case SimdOperation::Fn_shiftLeftByScalar: + return EmitSimdShift(f, type, MSimdShift::lsh); + case SimdOperation::Fn_shiftRightByScalar: + return EmitSimdShift(f, type, MSimdShift::rshForSign(sign)); +#define _CASE(OP) \ + case SimdOperation::Fn_##OP: \ + return EmitSimdBinaryComp(f, type, MSimdBinaryComp::OP, sign); + FOREACH_COMP_SIMD_OP(_CASE) +#undef _CASE + case SimdOperation::Fn_and: + return EmitSimdBinary(f, type, MSimdBinaryBitwise::and_); + case SimdOperation::Fn_or: + return EmitSimdBinary(f, type, MSimdBinaryBitwise::or_); + case SimdOperation::Fn_xor: + return EmitSimdBinary(f, type, MSimdBinaryBitwise::xor_); +#define _CASE(OP) \ + case SimdOperation::Fn_##OP: \ + return EmitSimdBinary(f, type, MSimdBinaryArith::Op_##OP); + FOREACH_NUMERIC_SIMD_BINOP(_CASE) + FOREACH_FLOAT_SIMD_BINOP(_CASE) +#undef _CASE + case SimdOperation::Fn_addSaturate: + return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::add, sign); + case SimdOperation::Fn_subSaturate: + return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::sub, sign); + case SimdOperation::Fn_fromFloat32x4: + return EmitSimdConvert(f, ValType::F32x4, type, sign); + case SimdOperation::Fn_fromInt32x4: + return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Signed); + case SimdOperation::Fn_fromUint32x4: + return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Unsigned); + case SimdOperation::Fn_fromInt8x16Bits: + case SimdOperation::Fn_fromUint8x16Bits: + return EmitSimdBitcast(f, ValType::I8x16, type); + case SimdOperation::Fn_fromUint16x8Bits: + case SimdOperation::Fn_fromInt16x8Bits: + return EmitSimdBitcast(f, ValType::I16x8, type); + case SimdOperation::Fn_fromInt32x4Bits: + case SimdOperation::Fn_fromUint32x4Bits: + return EmitSimdBitcast(f, ValType::I32x4, type); + case SimdOperation::Fn_fromFloat32x4Bits: + return EmitSimdBitcast(f, ValType::F32x4, type); + case SimdOperation::Fn_load3: + case SimdOperation::Fn_store3: + case SimdOperation::Fn_fromFloat64x2Bits: + MOZ_CRASH("NYI"); + } + MOZ_CRASH("unexpected opcode"); +} + static bool EmitGrowMemory(FunctionCompiler& f) { @@ -2635,7 +3432,7 @@ EmitAtomicCmpXchg(FunctionCompiler& f, ValType type, Scalar::Type viewType) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); auto* ins = f.atomicCompareExchangeHeap(addr.base, &access, type, oldValue, newValue); if (!f.inDeadCode() && !ins) return false; @@ -2652,7 +3449,7 @@ EmitAtomicLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Load()); + /*numSimdExprs=*/ 0, Synchronization::Load()); auto* ins = f.load(addr.base, &access, type); if (!f.inDeadCode() && !ins) return false; @@ -2670,7 +3467,7 @@ EmitAtomicRMW(FunctionCompiler& f, ValType type, Scalar::Type viewType, jit::Ato return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); auto* ins = f.atomicBinopHeap(op, addr.base, &access, type, value); if (!f.inDeadCode() && !ins) return false; @@ -2688,7 +3485,7 @@ EmitAtomicStore(FunctionCompiler& f, ValType type, Scalar::Type viewType) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Store()); + /*numSimdExprs=*/ 0, Synchronization::Store()); f.store(addr.base, &access, value); return true; } @@ -2792,7 +3589,7 @@ EmitAtomicXchg(FunctionCompiler& f, ValType type, Scalar::Type viewType) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(), - Synchronization::Full()); + /*numSimdExprs=*/ 0, Synchronization::Full()); MDefinition* ins = f.atomicExchangeHeap(addr.base, &access, type, value); if (!f.inDeadCode() && !ins) return false; @@ -3546,6 +4343,102 @@ EmitBodyExprs(FunctionCompiler& f) case uint16_t(MozOp::I32AtomicsExchange): CHECK(EmitOldAtomicsExchange(f)); + // SIMD +#define CASE(TYPE, OP, SIGN) \ + case uint16_t(MozOp::TYPE##OP): \ + CHECK(EmitSimdOp(f, ValType::TYPE, SimdOperation::Fn_##OP, SIGN)); +#define I8x16CASE(OP) CASE(I8x16, OP, SimdSign::Signed) +#define I16x8CASE(OP) CASE(I16x8, OP, SimdSign::Signed) +#define I32x4CASE(OP) CASE(I32x4, OP, SimdSign::Signed) +#define F32x4CASE(OP) CASE(F32x4, OP, SimdSign::NotApplicable) +#define B8x16CASE(OP) CASE(B8x16, OP, SimdSign::NotApplicable) +#define B16x8CASE(OP) CASE(B16x8, OP, SimdSign::NotApplicable) +#define B32x4CASE(OP) CASE(B32x4, OP, SimdSign::NotApplicable) +#define ENUMERATE(TYPE, FORALL, DO) \ + case uint16_t(MozOp::TYPE##Constructor): \ + CHECK(EmitSimdOp(f, ValType::TYPE, SimdOperation::Constructor, SimdSign::NotApplicable)); \ + FORALL(DO) + + ENUMERATE(I8x16, FORALL_INT8X16_ASMJS_OP, I8x16CASE) + ENUMERATE(I16x8, FORALL_INT16X8_ASMJS_OP, I16x8CASE) + ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32x4CASE) + ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32x4CASE) + ENUMERATE(B8x16, FORALL_BOOL_SIMD_OP, B8x16CASE) + ENUMERATE(B16x8, FORALL_BOOL_SIMD_OP, B16x8CASE) + ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32x4CASE) + +#undef CASE +#undef I8x16CASE +#undef I16x8CASE +#undef I32x4CASE +#undef F32x4CASE +#undef B8x16CASE +#undef B16x8CASE +#undef B32x4CASE +#undef ENUMERATE + + case uint16_t(MozOp::I8x16Const): + CHECK(EmitI8x16Const(f)); + case uint16_t(MozOp::I16x8Const): + CHECK(EmitI16x8Const(f)); + case uint16_t(MozOp::I32x4Const): + CHECK(EmitI32x4Const(f)); + case uint16_t(MozOp::F32x4Const): + CHECK(EmitF32x4Const(f)); + case uint16_t(MozOp::B8x16Const): + CHECK(EmitB8x16Const(f)); + case uint16_t(MozOp::B16x8Const): + CHECK(EmitB16x8Const(f)); + case uint16_t(MozOp::B32x4Const): + CHECK(EmitB32x4Const(f)); + + case uint16_t(MozOp::I8x16addSaturateU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_addSaturate, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16subSaturateU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_subSaturate, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16shiftRightByScalarU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16lessThanU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_lessThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16lessThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16greaterThanU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_greaterThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16greaterThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I8x16extractLaneU): + CHECK(EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_extractLane, SimdSign::Unsigned)); + + case uint16_t(MozOp::I16x8addSaturateU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_addSaturate, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8subSaturateU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_subSaturate, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8shiftRightByScalarU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8lessThanU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_lessThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8lessThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8greaterThanU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_greaterThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8greaterThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I16x8extractLaneU): + CHECK(EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_extractLane, SimdSign::Unsigned)); + + case uint16_t(MozOp::I32x4shiftRightByScalarU): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned)); + case uint16_t(MozOp::I32x4lessThanU): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_lessThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I32x4lessThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I32x4greaterThanU): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_greaterThan, SimdSign::Unsigned)); + case uint16_t(MozOp::I32x4greaterThanOrEqualU): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned)); + case uint16_t(MozOp::I32x4fromFloat32x4U): + CHECK(EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_fromFloat32x4, SimdSign::Unsigned)); + default: return f.iter().unrecognizedOpcode(&op); } diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index b9ee2102f53c..f06bc9f7e6d0 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -45,6 +45,16 @@ #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" +#define WASM_CRASH_IF_SIMD_TYPES \ + case ValType::I8x16: \ + case ValType::B8x16: \ + case ValType::I16x8: \ + case ValType::B16x8: \ + case ValType::I32x4: \ + case ValType::B32x4: \ + case ValType::F32x4: \ + MOZ_CRASH("unexpected SIMD type") + using namespace js; using namespace js::jit; using namespace js::wasm; @@ -146,6 +156,7 @@ ToWebAssemblyValue(JSContext* cx, ValType targetType, HandleValue v, MutableHand } return true; } + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: case ValType::I64: { break; @@ -168,6 +179,7 @@ ToJSValue(const Val& val) if (!val.ptr()) return NullValue(); return ObjectValue(*(JSObject*)val.ptr()); + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: case ValType::I64: break; @@ -2159,6 +2171,7 @@ WasmGlobalObject::trace(JSTracer* trc, JSObject* obj) case ValType::I64: case ValType::F64: break; + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: MOZ_CRASH("Ref NYI"); } @@ -2214,6 +2227,7 @@ WasmGlobalObject::create(JSContext* cx, HandleVal hval, bool isMutable) if (cell->ptr) JSObject::writeBarrierPost(&cell->ptr, nullptr, cell->ptr); break; + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: MOZ_CRASH("Ref NYI"); } @@ -2296,6 +2310,7 @@ WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) case ValType::F32: globalVal = Val(float(0.0)); break; case ValType::F64: globalVal = Val(double(0.0)); break; case ValType::AnyRef: globalVal = Val(nullptr); break; + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: MOZ_CRASH("Ref NYI"); } } @@ -2327,6 +2342,7 @@ WasmGlobalObject::valueGetterImpl(JSContext* cx, const CallArgs& args) case ValType::I64: JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE); return false; + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: MOZ_CRASH("Ref NYI"); } @@ -2377,6 +2393,7 @@ WasmGlobalObject::valueSetterImpl(JSContext* cx, const CallArgs& args) JSObject::writeBarrierPost(&cell->ptr, prevPtr, cell->ptr); break; } + WASM_CRASH_IF_SIMD_TYPES; case ValType::I64: MOZ_CRASH("unexpected i64 when setting global's value"); case ValType::Ref: @@ -2432,6 +2449,7 @@ WasmGlobalObject::val(MutableHandleVal outval) const case ValType::F32: outval.set(Val(cell->f32)); return; case ValType::F64: outval.set(Val(cell->f64)); return; case ValType::AnyRef: outval.set(Val(cell->ptr)); return; + WASM_CRASH_IF_SIMD_TYPES; case ValType::Ref: MOZ_CRASH("Ref NYI"); } MOZ_CRASH("unexpected Global type"); diff --git a/js/src/wasm/WasmOpIter.cpp b/js/src/wasm/WasmOpIter.cpp index 31d3e4600b53..614be200bd86 100644 --- a/js/src/wasm/WasmOpIter.cpp +++ b/js/src/wasm/WasmOpIter.cpp @@ -356,15 +356,86 @@ wasm::Classify(OpBytes op) break; case MozOp::TeeGlobal: return OpKind::TeeGlobal; + case MozOp::I8x16Const: + return OpKind::I8x16; + case MozOp::I16x8Const: + return OpKind::I16x8; + case MozOp::I32x4Const: + return OpKind::I32x4; + case MozOp::B8x16Const: + return OpKind::B8x16; + case MozOp::B16x8Const: + return OpKind::B16x8; + case MozOp::B32x4Const: + return OpKind::B32x4; + case MozOp::F32x4Const: + return OpKind::F32x4; case MozOp::I32BitNot: case MozOp::I32Abs: case MozOp::I32Neg: + case MozOp::I8x16neg: + case MozOp::I8x16not: + case MozOp::I16x8neg: + case MozOp::I16x8not: + case MozOp::I32x4neg: + case MozOp::I32x4not: + case MozOp::F32x4neg: + case MozOp::F32x4sqrt: + case MozOp::F32x4abs: + case MozOp::F32x4reciprocalApproximation: + case MozOp::F32x4reciprocalSqrtApproximation: + case MozOp::B8x16not: + case MozOp::B16x8not: + case MozOp::B32x4not: return OpKind::Unary; case MozOp::I32Min: case MozOp::I32Max: case MozOp::F64Mod: case MozOp::F64Pow: case MozOp::F64Atan2: + case MozOp::I8x16add: + case MozOp::I8x16sub: + case MozOp::I8x16mul: + case MozOp::I8x16addSaturate: + case MozOp::I8x16subSaturate: + case MozOp::I8x16addSaturateU: + case MozOp::I8x16subSaturateU: + case MozOp::I8x16and: + case MozOp::I8x16or: + case MozOp::I8x16xor: + case MozOp::I16x8add: + case MozOp::I16x8sub: + case MozOp::I16x8mul: + case MozOp::I16x8addSaturate: + case MozOp::I16x8subSaturate: + case MozOp::I16x8addSaturateU: + case MozOp::I16x8subSaturateU: + case MozOp::I16x8and: + case MozOp::I16x8or: + case MozOp::I16x8xor: + case MozOp::I32x4add: + case MozOp::I32x4sub: + case MozOp::I32x4mul: + case MozOp::I32x4and: + case MozOp::I32x4or: + case MozOp::I32x4xor: + case MozOp::F32x4add: + case MozOp::F32x4sub: + case MozOp::F32x4mul: + case MozOp::F32x4div: + case MozOp::F32x4min: + case MozOp::F32x4max: + case MozOp::F32x4minNum: + case MozOp::F32x4maxNum: + case MozOp::B8x16and: + case MozOp::B8x16or: + case MozOp::B8x16xor: + case MozOp::B16x8and: + case MozOp::B16x8or: + case MozOp::B16x8xor: + case MozOp::B32x4and: + case MozOp::B32x4or: + case MozOp::B32x4xor: return OpKind::Binary; case MozOp::F64Sin: case MozOp::F64Cos: @@ -387,6 +458,45 @@ wasm::Classify(OpBytes op) case MozOp::F32TeeStoreF64: case MozOp::F64TeeStoreF32: return OpKind::TeeStore; + case MozOp::I32x4fromFloat32x4: + case MozOp::I32x4fromFloat32x4U: + case MozOp::F32x4fromInt32x4: + case MozOp::F32x4fromUint32x4: + case MozOp::I32x4fromFloat32x4Bits: + case MozOp::I32x4fromInt8x16Bits: + case MozOp::I32x4fromInt16x8Bits: + case MozOp::I16x8fromInt8x16Bits: + case MozOp::I16x8fromInt32x4Bits: + case MozOp::I16x8fromFloat32x4Bits: + case MozOp::I8x16fromInt16x8Bits: + case MozOp::I8x16fromInt32x4Bits: + case MozOp::I8x16fromFloat32x4Bits: + case MozOp::F32x4fromInt8x16Bits: + case MozOp::F32x4fromInt16x8Bits: + case MozOp::F32x4fromInt32x4Bits: + return OpKind::Conversion; + case MozOp::I8x16load: + case MozOp::I16x8load: + case MozOp::I32x4load: + case MozOp::I32x4load1: + case MozOp::I32x4load2: + case MozOp::I32x4load3: + case MozOp::F32x4load: + case MozOp::F32x4load1: + case MozOp::F32x4load2: + case MozOp::F32x4load3: + return OpKind::Load; + case MozOp::I8x16store: + case MozOp::I16x8store: + case MozOp::I32x4store: + case MozOp::I32x4store1: + case MozOp::I32x4store2: + case MozOp::I32x4store3: + case MozOp::F32x4store: + case MozOp::F32x4store1: + case MozOp::F32x4store2: + case MozOp::F32x4store3: + return OpKind::TeeStore; case MozOp::OldCallDirect: return OpKind::OldCallDirect; case MozOp::OldCallIndirect: @@ -401,6 +511,116 @@ wasm::Classify(OpBytes op) return OpKind::OldAtomicCompareExchange; case MozOp::I32AtomicsExchange: return OpKind::OldAtomicExchange; + case MozOp::I8x16extractLane: + case MozOp::I8x16extractLaneU: + case MozOp::I16x8extractLane: + case MozOp::I16x8extractLaneU: + case MozOp::I32x4extractLane: + case MozOp::F32x4extractLane: + case MozOp::B8x16extractLane: + case MozOp::B16x8extractLane: + case MozOp::B32x4extractLane: + return OpKind::ExtractLane; + case MozOp::I8x16replaceLane: + case MozOp::I16x8replaceLane: + case MozOp::I32x4replaceLane: + case MozOp::F32x4replaceLane: + case MozOp::B8x16replaceLane: + case MozOp::B16x8replaceLane: + case MozOp::B32x4replaceLane: + return OpKind::ReplaceLane; + case MozOp::I8x16swizzle: + case MozOp::I16x8swizzle: + case MozOp::I32x4swizzle: + case MozOp::F32x4swizzle: + return OpKind::Swizzle; + case MozOp::I8x16shuffle: + case MozOp::I16x8shuffle: + case MozOp::I32x4shuffle: + case MozOp::F32x4shuffle: + return OpKind::Shuffle; + case MozOp::I16x8check: + case MozOp::I16x8splat: + case MozOp::I32x4check: + case MozOp::I32x4splat: + case MozOp::I8x16check: + case MozOp::I8x16splat: + case MozOp::F32x4check: + case MozOp::F32x4splat: + case MozOp::B16x8check: + case MozOp::B16x8splat: + case MozOp::B32x4check: + case MozOp::B32x4splat: + case MozOp::B8x16check: + case MozOp::B8x16splat: + return OpKind::Splat; + case MozOp::I8x16select: + case MozOp::I16x8select: + case MozOp::I32x4select: + case MozOp::F32x4select: + return OpKind::SimdSelect; + case MozOp::I8x16Constructor: + case MozOp::I16x8Constructor: + case MozOp::I32x4Constructor: + case MozOp::F32x4Constructor: + case MozOp::B8x16Constructor: + case MozOp::B16x8Constructor: + case MozOp::B32x4Constructor: + return OpKind::SimdCtor; + case MozOp::B8x16allTrue: + case MozOp::B8x16anyTrue: + case MozOp::B16x8allTrue: + case MozOp::B16x8anyTrue: + case MozOp::B32x4allTrue: + case MozOp::B32x4anyTrue: + return OpKind::SimdBooleanReduction; + case MozOp::I8x16shiftLeftByScalar: + case MozOp::I8x16shiftRightByScalar: + case MozOp::I8x16shiftRightByScalarU: + case MozOp::I16x8shiftLeftByScalar: + case MozOp::I16x8shiftRightByScalar: + case MozOp::I16x8shiftRightByScalarU: + case MozOp::I32x4shiftLeftByScalar: + case MozOp::I32x4shiftRightByScalar: + case MozOp::I32x4shiftRightByScalarU: + return OpKind::SimdShiftByScalar; + case MozOp::I8x16equal: + case MozOp::I8x16notEqual: + case MozOp::I8x16greaterThan: + case MozOp::I8x16greaterThanOrEqual: + case MozOp::I8x16lessThan: + case MozOp::I8x16lessThanOrEqual: + case MozOp::I8x16greaterThanU: + case MozOp::I8x16greaterThanOrEqualU: + case MozOp::I8x16lessThanU: + case MozOp::I8x16lessThanOrEqualU: + case MozOp::I16x8equal: + case MozOp::I16x8notEqual: + case MozOp::I16x8greaterThan: + case MozOp::I16x8greaterThanOrEqual: + case MozOp::I16x8lessThan: + case MozOp::I16x8lessThanOrEqual: + case MozOp::I16x8greaterThanU: + case MozOp::I16x8greaterThanOrEqualU: + case MozOp::I16x8lessThanU: + case MozOp::I16x8lessThanOrEqualU: + case MozOp::I32x4equal: + case MozOp::I32x4notEqual: + case MozOp::I32x4greaterThan: + case MozOp::I32x4greaterThanOrEqual: + case MozOp::I32x4lessThan: + case MozOp::I32x4lessThanOrEqual: + case MozOp::I32x4greaterThanU: + case MozOp::I32x4greaterThanOrEqualU: + case MozOp::I32x4lessThanU: + case MozOp::I32x4lessThanOrEqualU: + case MozOp::F32x4equal: + case MozOp::F32x4notEqual: + case MozOp::F32x4greaterThan: + case MozOp::F32x4greaterThanOrEqual: + case MozOp::F32x4lessThan: + case MozOp::F32x4lessThanOrEqual: + return OpKind::SimdComparison; } break; } diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h index ae119aded6d3..f5bb7071ba5c 100644 --- a/js/src/wasm/WasmOpIter.h +++ b/js/src/wasm/WasmOpIter.h @@ -51,6 +51,13 @@ class StackType case TypeCode::I64: case TypeCode::F32: case TypeCode::F64: + case TypeCode::I8x16: + case TypeCode::I16x8: + case TypeCode::I32x4: + case TypeCode::F32x4: + case TypeCode::B8x16: + case TypeCode::B16x8: + case TypeCode::B32x4: case TypeCode::AnyRef: case TypeCode::Ref: case TypeCode::Limit: @@ -68,6 +75,14 @@ class StackType F32 = uint8_t(ValType::F32), F64 = uint8_t(ValType::F64), + I8x16 = uint8_t(ValType::I8x16), + I16x8 = uint8_t(ValType::I16x8), + I32x4 = uint8_t(ValType::I32x4), + F32x4 = uint8_t(ValType::F32x4), + B8x16 = uint8_t(ValType::B8x16), + B16x8 = uint8_t(ValType::B16x8), + B32x4 = uint8_t(ValType::B32x4), + AnyRef = uint8_t(ValType::AnyRef), Ref = uint8_t(ValType::Ref), @@ -143,6 +158,13 @@ enum class OpKind { I64, F32, F64, + I8x16, + I16x8, + I32x4, + F32x4, + B8x16, + B16x8, + B32x4, Br, BrIf, BrTable, @@ -187,6 +209,11 @@ enum class OpKind { Swizzle, Shuffle, Splat, + SimdSelect, + SimdCtor, + SimdBooleanReduction, + SimdShiftByScalar, + SimdComparison, MemCopy, MemFill, RefNull, @@ -378,6 +405,18 @@ class MOZ_STACK_CLASS OpIter : private Policy MOZ_MUST_USE bool readFixedF64(double* out) { return d_.readFixedF64(out); } + MOZ_MUST_USE bool readFixedI8x16(I8x16* out) { + return d_.readFixedI8x16(out); + } + MOZ_MUST_USE bool readFixedI16x8(I16x8* out) { + return d_.readFixedI16x8(out); + } + MOZ_MUST_USE bool readFixedI32x4(I32x4* out) { + return d_.readFixedI32x4(out); + } + MOZ_MUST_USE bool readFixedF32x4(F32x4* out) { + return d_.readFixedF32x4(out); + } MOZ_MUST_USE bool readAtomicViewType(Scalar::Type* viewType) { uint8_t x; @@ -553,6 +592,13 @@ class MOZ_STACK_CLASS OpIter : private Policy MOZ_MUST_USE bool readI64Const(int64_t* i64); MOZ_MUST_USE bool readF32Const(float* f32); MOZ_MUST_USE bool readF64Const(double* f64); + MOZ_MUST_USE bool readI8x16Const(I8x16* i8x16); + MOZ_MUST_USE bool readI16x8Const(I16x8* i16x8); + MOZ_MUST_USE bool readI32x4Const(I32x4* i32x4); + MOZ_MUST_USE bool readF32x4Const(F32x4* f32x4); + MOZ_MUST_USE bool readB8x16Const(I8x16* i8x16); + MOZ_MUST_USE bool readB16x8Const(I16x8* i16x8); + MOZ_MUST_USE bool readB32x4Const(I32x4* i32x4); MOZ_MUST_USE bool readRefNull(ValType* type); MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues); MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues); @@ -597,6 +643,24 @@ class MOZ_STACK_CLASS OpIter : private Policy MOZ_MUST_USE bool readOldAtomicExchange(LinearMemoryAddress* addr, Scalar::Type* viewType, Value* newValue); + MOZ_MUST_USE bool readSimdComparison(ValType simdType, Value* lhs, + Value* rhs); + MOZ_MUST_USE bool readSimdShiftByScalar(ValType simdType, Value* lhs, + Value* rhs); + MOZ_MUST_USE bool readSimdBooleanReduction(ValType simdType, Value* input); + MOZ_MUST_USE bool readExtractLane(ValType simdType, uint8_t* lane, + Value* vector); + MOZ_MUST_USE bool readReplaceLane(ValType simdType, uint8_t* lane, + Value* vector, Value* scalar); + MOZ_MUST_USE bool readSplat(ValType simdType, Value* scalar); + MOZ_MUST_USE bool readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector); + MOZ_MUST_USE bool readShuffle(ValType simdType, uint8_t (* lanes)[16], + Value* lhs, Value* rhs); + MOZ_MUST_USE bool readSimdSelect(ValType simdType, Value* trueValue, + Value* falseValue, + Value* condition); + MOZ_MUST_USE bool readSimdCtor(ValType elementType, uint32_t numElements, ValType simdType, + ValueVector* argValues); MOZ_MUST_USE bool readMemCopy(Value* dest, Value* src, Value* len); MOZ_MUST_USE bool readMemFill(Value* start, Value* val, Value* len); @@ -942,6 +1006,13 @@ OpIter::readBlockType(ExprType* type) case uint8_t(ExprType::I64): case uint8_t(ExprType::F32): case uint8_t(ExprType::F64): + case uint8_t(ExprType::I8x16): + case uint8_t(ExprType::I16x8): + case uint8_t(ExprType::I32x4): + case uint8_t(ExprType::F32x4): + case uint8_t(ExprType::B8x16): + case uint8_t(ExprType::B16x8): + case uint8_t(ExprType::B32x4): known = true; break; case uint8_t(ExprType::Ref): @@ -1620,6 +1691,76 @@ OpIter::readF64Const(double* f64) push(ValType::F64); } +template +inline bool +OpIter::readI8x16Const(I8x16* i8x16) +{ + MOZ_ASSERT(Classify(op_) == OpKind::I8x16); + + return readFixedI8x16(i8x16) && + push(ValType::I8x16); +} + +template +inline bool +OpIter::readI16x8Const(I16x8* i16x8) +{ + MOZ_ASSERT(Classify(op_) == OpKind::I16x8); + + return readFixedI16x8(i16x8) && + push(ValType::I16x8); +} + +template +inline bool +OpIter::readI32x4Const(I32x4* i32x4) +{ + MOZ_ASSERT(Classify(op_) == OpKind::I32x4); + + return readFixedI32x4(i32x4) && + push(ValType::I32x4); +} + +template +inline bool +OpIter::readF32x4Const(F32x4* f32x4) +{ + MOZ_ASSERT(Classify(op_) == OpKind::F32x4); + + return readFixedF32x4(f32x4) && + push(ValType::F32x4); +} + +template +inline bool +OpIter::readB8x16Const(I8x16* i8x16) +{ + MOZ_ASSERT(Classify(op_) == OpKind::B8x16); + + return readFixedI8x16(i8x16) && + push(ValType::B8x16); +} + +template +inline bool +OpIter::readB16x8Const(I16x8* i16x8) +{ + MOZ_ASSERT(Classify(op_) == OpKind::B16x8); + + return readFixedI16x8(i16x8) && + push(ValType::B16x8); +} + +template +inline bool +OpIter::readB32x4Const(I32x4* i32x4) +{ + MOZ_ASSERT(Classify(op_) == OpKind::B32x4); + + return readFixedI32x4(i32x4) && + push(ValType::B32x4); +} + template inline bool OpIter::readRefNull(ValType* type) @@ -2003,6 +2144,203 @@ OpIter::readOldAtomicExchange(LinearMemoryAddress* addr, Scalar:: return true; } +template +inline bool +OpIter::readSimdComparison(ValType simdType, Value* lhs, Value* rhs) +{ + MOZ_ASSERT(Classify(op_) == OpKind::SimdComparison); + + if (!popWithType(simdType, rhs)) + return false; + + if (!popWithType(simdType, lhs)) + return false; + + infalliblePush(SimdBoolType(simdType)); + + return true; +} + +template +inline bool +OpIter::readSimdShiftByScalar(ValType simdType, Value* lhs, Value* rhs) +{ + MOZ_ASSERT(Classify(op_) == OpKind::SimdShiftByScalar); + + if (!popWithType(ValType::I32, rhs)) + return false; + + if (!popWithType(simdType, lhs)) + return false; + + infalliblePush(simdType); + + return true; +} + +template +inline bool +OpIter::readSimdBooleanReduction(ValType simdType, Value* input) +{ + MOZ_ASSERT(Classify(op_) == OpKind::SimdBooleanReduction); + + if (!popWithType(simdType, input)) + return false; + + infalliblePush(ValType::I32); + + return true; +} + +template +inline bool +OpIter::readExtractLane(ValType simdType, uint8_t* lane, Value* vector) +{ + MOZ_ASSERT(Classify(op_) == OpKind::ExtractLane); + + uint32_t laneBits; + if (!readVarU32(&laneBits)) + return false; + + if (laneBits >= NumSimdElements(simdType)) + return fail("simd lane out of bounds for simd type"); + + *lane = uint8_t(laneBits); + + if (!popWithType(simdType, vector)) + return false; + + infalliblePush(SimdElementType(simdType)); + return true; +} + +template +inline bool +OpIter::readReplaceLane(ValType simdType, uint8_t* lane, Value* vector, Value* scalar) +{ + MOZ_ASSERT(Classify(op_) == OpKind::ReplaceLane); + + uint32_t laneBits; + if (!readVarU32(&laneBits)) + return false; + + if (laneBits >= NumSimdElements(simdType)) + return fail("simd lane out of bounds for simd type"); + + *lane = uint8_t(laneBits); + + if (!popWithType(SimdElementType(simdType), scalar)) + return false; + + if (!popWithType(simdType, vector)) + return false; + + infalliblePush(simdType); + return true; +} + +template +inline bool +OpIter::readSplat(ValType simdType, Value* scalar) +{ + MOZ_ASSERT(Classify(op_) == OpKind::Splat); + + if (!popWithType(SimdElementType(simdType), scalar)) + return false; + + infalliblePush(simdType); + + return true; +} + +template +inline bool +OpIter::readSwizzle(ValType simdType, uint8_t (* lanes)[16], Value* vector) +{ + MOZ_ASSERT(Classify(op_) == OpKind::Swizzle); + + uint32_t numSimdLanes = NumSimdElements(simdType); + MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes)); + for (uint32_t i = 0; i < numSimdLanes; ++i) { + if (!readFixedU8(&(*lanes)[i])) + return fail("unable to read swizzle lane"); + if ((*lanes)[i] >= numSimdLanes) + return fail("swizzle index out of bounds"); + } + + if (!popWithType(simdType, vector)) + return false; + + infalliblePush(simdType); + + return true; +} + +template +inline bool +OpIter::readShuffle(ValType simdType, uint8_t (* lanes)[16], Value* lhs, Value* rhs) +{ + MOZ_ASSERT(Classify(op_) == OpKind::Shuffle); + + uint32_t numSimdLanes = NumSimdElements(simdType); + MOZ_ASSERT(numSimdLanes <= mozilla::ArrayLength(*lanes)); + for (uint32_t i = 0; i < numSimdLanes; ++i) { + if (!readFixedU8(&(*lanes)[i])) + return fail("unable to read shuffle lane"); + if ((*lanes)[i] >= numSimdLanes * 2) + return fail("shuffle index out of bounds"); + } + + if (!popWithType(simdType, rhs)) + return false; + + if (!popWithType(simdType, lhs)) + return false; + + infalliblePush(simdType); + + return true; +} + +template +inline bool +OpIter::readSimdSelect(ValType simdType, Value* trueValue, Value* falseValue, + Value* condition) +{ + MOZ_ASSERT(Classify(op_) == OpKind::SimdSelect); + + if (!popWithType(simdType, falseValue)) + return false; + if (!popWithType(simdType, trueValue)) + return false; + if (!popWithType(SimdBoolType(simdType), condition)) + return false; + + infalliblePush(simdType); + + return true; +} + +template +inline bool +OpIter::readSimdCtor(ValType elementType, uint32_t numElements, ValType simdType, + ValueVector* argValues) +{ + MOZ_ASSERT(Classify(op_) == OpKind::SimdCtor); + + if (!argValues->resize(numElements)) + return false; + + for (int32_t i = numElements - 1; i >= 0; i--) { + if (!popWithType(elementType, &(*argValues)[i])) + return false; + } + + infalliblePush(simdType); + + return true; +} + template inline bool OpIter::readMemCopy(Value* dest, Value* src, Value* len) diff --git a/js/src/wasm/WasmSignalHandlers.cpp b/js/src/wasm/WasmSignalHandlers.cpp index 2732a1e60e98..93bf542c4540 100644 --- a/js/src/wasm/WasmSignalHandlers.cpp +++ b/js/src/wasm/WasmSignalHandlers.cpp @@ -742,8 +742,9 @@ HandleOutOfBounds(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress, BytecodeOffset bytecode; if (!segment->code().lookupTrap(pc, &trap, &bytecode)) { // If there is no associated TrapSite for the faulting PC, this must be - // an Atomics access. When these are converted to non-experimental wasm - // features, this case, as well as outOfBoundsCode, can be removed. + // experimental SIMD.js or Atomics. When these are converted to + // non-experimental wasm features, this case, as well as outOfBoundsCode, + // can be removed. activation->startWasmTrap(Trap::OutOfBounds, 0, ToRegisterState(context)); *ppc = segment->outOfBoundsCode(); return true; @@ -870,8 +871,9 @@ HandleOutOfBounds(CONTEXT* context, uint8_t* pc, uint8_t* faultingAddress, // Assign the JS-defined result value to the destination register // (ToInt32(undefined) or ToNumber(undefined), determined by the // type of the destination register). Very conveniently, we can - // infer the type from the register class, so the only types using - // FP registers are float32 and double. + // infer the type from the register class, since all SIMD accesses + // throw on out of bounds (see above), so the only types using FP + // registers are float32 and double. SetRegisterToCoercedUndefined(context, access.size(), access.otherOperand()); break; case Disassembler::HeapAccess::Store: diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp index 922f5c1fc5df..53691e7f1b53 100644 --- a/js/src/wasm/WasmStubs.cpp +++ b/js/src/wasm/WasmStubs.cpp @@ -109,6 +109,17 @@ SetupABIArguments(MacroAssembler& masm, const FuncExport& fe, Register argv, Reg static_assert(sizeof(ExportArg) >= jit::Simd128DataSize, "ExportArg must be big enough to store SIMD values"); switch (type) { + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + masm.loadUnalignedSimd128Int(src, iter->fpu()); + break; + case MIRType::Float32x4: + masm.loadUnalignedSimd128Float(src, iter->fpu()); + break; case MIRType::Double: masm.loadDouble(src, iter->fpu()); break; @@ -155,6 +166,21 @@ SetupABIArguments(MacroAssembler& masm, const FuncExport& fe, Register argv, Reg masm.storeFloat32(ScratchFloat32Reg, Address(masm.getStackPointer(), iter->offsetFromArgBase())); break; + case MIRType::Int8x16: + case MIRType::Int16x8: + case MIRType::Int32x4: + case MIRType::Bool8x16: + case MIRType::Bool16x8: + case MIRType::Bool32x4: + masm.loadUnalignedSimd128Int(src, ScratchSimd128Reg); + masm.storeAlignedSimd128Int( + ScratchSimd128Reg, Address(masm.getStackPointer(), iter->offsetFromArgBase())); + break; + case MIRType::Float32x4: + masm.loadUnalignedSimd128Float(src, ScratchSimd128Reg); + masm.storeAlignedSimd128Float( + ScratchSimd128Reg, Address(masm.getStackPointer(), iter->offsetFromArgBase())); + break; default: MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected stack arg type"); } @@ -190,6 +216,19 @@ StoreABIReturn(MacroAssembler& masm, const FuncExport& fe, Register argv) case ExprType::AnyRef: masm.storePtr(ReturnReg, Address(argv, 0)); break; + case ExprType::I8x16: + case ExprType::I16x8: + case ExprType::I32x4: + case ExprType::B8x16: + case ExprType::B16x8: + case ExprType::B32x4: + // We don't have control on argv alignment, do an unaligned access. + masm.storeUnalignedSimd128Int(ReturnSimd128Reg, Address(argv, 0)); + break; + case ExprType::F32x4: + // We don't have control on argv alignment, do an unaligned access. + masm.storeUnalignedSimd128Float(ReturnSimd128Reg, Address(argv, 0)); + break; case ExprType::Limit: MOZ_CRASH("Limit"); } @@ -794,6 +833,13 @@ GenerateJitEntry(MacroAssembler& masm, size_t funcExportIndex, const FuncExport& MOZ_CRASH("return anyref in jitentry NYI"); break; case ExprType::I64: + case ExprType::I8x16: + case ExprType::I16x8: + case ExprType::I32x4: + case ExprType::B8x16: + case ExprType::B16x8: + case ExprType::B32x4: + case ExprType::F32x4: MOZ_CRASH("unexpected return type when calling from ion to wasm"); case ExprType::Limit: MOZ_CRASH("Limit"); @@ -1166,6 +1212,14 @@ GenerateImportInterpExit(MacroAssembler& masm, const FuncImport& fi, uint32_t fu masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, throwLabel); masm.loadPtr(argv, ReturnReg); break; + case ExprType::I8x16: + case ExprType::I16x8: + case ExprType::I32x4: + case ExprType::F32x4: + case ExprType::B8x16: + case ExprType::B16x8: + case ExprType::B32x4: + MOZ_CRASH("SIMD types shouldn't be returned from a FFI"); case ExprType::Limit: MOZ_CRASH("Limit"); } @@ -1339,6 +1393,14 @@ GenerateImportJitExit(MacroAssembler& masm, const FuncImport& fi, Label* throwLa case ExprType::AnyRef: MOZ_CRASH("anyref returned by import (jit exit) NYI"); break; + case ExprType::I8x16: + case ExprType::I16x8: + case ExprType::I32x4: + case ExprType::F32x4: + case ExprType::B8x16: + case ExprType::B16x8: + case ExprType::B32x4: + MOZ_CRASH("SIMD types shouldn't be returned from an import"); case ExprType::Limit: MOZ_CRASH("Limit"); } @@ -1536,13 +1598,11 @@ static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too. static const LiveRegisterSet RegsToPreserve( GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::StackPointer) | (uint32_t(1) << Registers::lr))), - FloatRegisterSet(FloatRegisters::AllDoubleMask)); -static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too"); + FloatRegisterSet(FloatRegisters::AllMask)); #else static const LiveRegisterSet RegsToPreserve( GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)), - FloatRegisterSet(FloatRegisters::AllDoubleMask)); -static_assert(!SupportsSimd, "high lanes of SIMD registers need to be saved too"); + FloatRegisterSet(FloatRegisters::AllMask)); #endif // Generate a stub which calls WasmReportTrap() and can be executed by having @@ -1599,12 +1659,13 @@ GenerateTrapExit(MacroAssembler& masm, Label* throwLabel, Offsets* offsets) } // Generate a stub which is only used by the signal handlers to handle out of -// bounds access by Atomics and unaligned accesses on ARM. This stub is -// executed by direct PC transfer from the faulting memory access and thus the -// stack depth is unknown. Since JitActivation::packedExitFP() is not set -// before calling the error reporter, the current wasm activation will be lost. -// This stub should be removed when Atomics are moved to wasm and given proper -// traps and when we use a non-faulting strategy for unaligned ARM access. +// bounds access by experimental SIMD.js and Atomics and unaligned accesses on +// ARM. This stub is executed by direct PC transfer from the faulting memory +// access and thus the stack depth is unknown. Since +// JitActivation::packedExitFP() is not set before calling the error reporter, +// the current wasm activation will be lost. This stub should be removed when +// SIMD.js and Atomics are moved to wasm and given proper traps and when we use +// a non-faulting strategy for unaligned ARM access. static bool GenerateGenericMemoryAccessTrap(MacroAssembler& masm, SymbolicAddress reporter, Label* throwLabel, Offsets* offsets) diff --git a/js/src/wasm/WasmTypes.cpp b/js/src/wasm/WasmTypes.cpp index a82e89bf3322..6e49ea127af5 100644 --- a/js/src/wasm/WasmTypes.cpp +++ b/js/src/wasm/WasmTypes.cpp @@ -68,6 +68,13 @@ Val::Val(const LitVal& val) case ValType::F32: u.f32_ = val.f32(); return; case ValType::I64: u.i64_ = val.i64(); return; case ValType::F64: u.f64_ = val.f64(); return; + case ValType::I8x16: + case ValType::B8x16: + case ValType::I16x8: + case ValType::B16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B32x4: memcpy(&u, val.rawSimd(), jit::Simd128DataSize); return; case ValType::AnyRef: u.ptr_ = val.ptr(); return; case ValType::Ref: break; } @@ -86,6 +93,15 @@ Val::writePayload(uint8_t* dst) const case ValType::F64: memcpy(dst, &u.i64_, sizeof(u.i64_)); return; + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: + memcpy(dst, &u, jit::Simd128DataSize); + return; case ValType::Ref: case ValType::AnyRef: MOZ_ASSERT(*(JSObject**)dst == nullptr, "should be null so no need for a pre-barrier"); @@ -221,6 +237,13 @@ IsImmediateType(ValType vt) case ValType::F64: case ValType::AnyRef: return true; + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: case ValType::Ref: return false; } @@ -242,6 +265,13 @@ EncodeImmediateType(ValType vt) return 3; case ValType::AnyRef: return 4; + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: case ValType::Ref: break; } diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h index edf9bd62bc47..e873f442a5fb 100644 --- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -87,6 +87,11 @@ using mozilla::PodEqual; using mozilla::Some; using mozilla::Unused; +typedef int8_t I8x16[16]; +typedef int16_t I16x8[8]; +typedef int32_t I32x4[4]; +typedef float F32x4[4]; + class Code; class DebugState; class GeneratedSourceMap; @@ -245,6 +250,13 @@ class ExprType case TypeCode::I64: case TypeCode::F32: case TypeCode::F64: + case TypeCode::I8x16: + case TypeCode::I16x8: + case TypeCode::I32x4: + case TypeCode::F32x4: + case TypeCode::B8x16: + case TypeCode::B16x8: + case TypeCode::B32x4: case TypeCode::AnyRef: case TypeCode::Ref: case TypeCode::BlockVoid: @@ -267,6 +279,14 @@ class ExprType AnyRef = uint8_t(TypeCode::AnyRef), Ref = uint8_t(TypeCode::Ref), + I8x16 = uint8_t(TypeCode::I8x16), + I16x8 = uint8_t(TypeCode::I16x8), + I32x4 = uint8_t(TypeCode::I32x4), + F32x4 = uint8_t(TypeCode::F32x4), + B8x16 = uint8_t(TypeCode::B8x16), + B16x8 = uint8_t(TypeCode::B16x8), + B32x4 = uint8_t(TypeCode::B32x4), + Limit = uint8_t(TypeCode::Limit) }; @@ -351,6 +371,13 @@ class ValType case TypeCode::I64: case TypeCode::F32: case TypeCode::F64: + case TypeCode::I8x16: + case TypeCode::I16x8: + case TypeCode::I32x4: + case TypeCode::F32x4: + case TypeCode::B8x16: + case TypeCode::B16x8: + case TypeCode::B32x4: case TypeCode::AnyRef: case TypeCode::Ref: return true; @@ -369,6 +396,14 @@ class ValType AnyRef = uint8_t(TypeCode::AnyRef), Ref = uint8_t(TypeCode::Ref), + + I8x16 = uint8_t(TypeCode::I8x16), + I16x8 = uint8_t(TypeCode::I16x8), + I32x4 = uint8_t(TypeCode::I32x4), + F32x4 = uint8_t(TypeCode::F32x4), + B8x16 = uint8_t(TypeCode::B8x16), + B16x8 = uint8_t(TypeCode::B16x8), + B32x4 = uint8_t(TypeCode::B32x4) }; ValType() : tc_(InvalidPackedTypeCode()) {} @@ -462,6 +497,14 @@ SizeOf(ValType vt) case ValType::I64: case ValType::F64: return 8; + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: + return 16; case ValType::AnyRef: case ValType::Ref: return sizeof(intptr_t); @@ -469,6 +512,89 @@ SizeOf(ValType vt) MOZ_CRASH("Invalid ValType"); } +static inline bool +IsSimdType(ValType vt) +{ + switch (vt.code()) { + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + case ValType::F32x4: + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: + return true; + default: + return false; + } +} + +static inline uint32_t +NumSimdElements(ValType vt) +{ + MOZ_ASSERT(IsSimdType(vt)); + switch (vt.code()) { + case ValType::I8x16: + case ValType::B8x16: + return 16; + case ValType::I16x8: + case ValType::B16x8: + return 8; + case ValType::I32x4: + case ValType::F32x4: + case ValType::B32x4: + return 4; + default: + MOZ_CRASH("Unhandled SIMD type"); + } +} + +static inline ValType +SimdElementType(ValType vt) +{ + MOZ_ASSERT(IsSimdType(vt)); + switch (vt.code()) { + case ValType::I8x16: + case ValType::I16x8: + case ValType::I32x4: + return ValType::I32; + case ValType::F32x4: + return ValType::F32; + case ValType::B8x16: + case ValType::B16x8: + case ValType::B32x4: + return ValType::I32; + default: + MOZ_CRASH("Unhandled SIMD type"); + } +} + +static inline ValType +SimdBoolType(ValType vt) +{ + MOZ_ASSERT(IsSimdType(vt)); + switch (vt.code()) { + case ValType::I8x16: + case ValType::B8x16: + return ValType::B8x16; + case ValType::I16x8: + case ValType::B16x8: + return ValType::B16x8; + case ValType::I32x4: + case ValType::F32x4: + case ValType::B32x4: + return ValType::B32x4; + default: + MOZ_CRASH("Unhandled SIMD type"); + } +} + +static inline bool +IsSimdBoolType(ValType vt) +{ + return vt == ValType::B8x16 || vt == ValType::B16x8 || vt == ValType::B32x4; +} + static inline jit::MIRType ToMIRType(ValType vt) { @@ -479,6 +605,13 @@ ToMIRType(ValType vt) case ValType::F64: return jit::MIRType::Double; case ValType::Ref: return jit::MIRType::Pointer; case ValType::AnyRef: return jit::MIRType::Pointer; + case ValType::I8x16: return jit::MIRType::Int8x16; + case ValType::I16x8: return jit::MIRType::Int16x8; + case ValType::I32x4: return jit::MIRType::Int32x4; + case ValType::F32x4: return jit::MIRType::Float32x4; + case ValType::B8x16: return jit::MIRType::Bool8x16; + case ValType::B16x8: return jit::MIRType::Bool16x8; + case ValType::B32x4: return jit::MIRType::Bool32x4; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type"); } @@ -509,6 +642,12 @@ NonVoidToValType(ExprType et) return ValType(et); } +static inline bool +IsSimdType(ExprType et) +{ + return IsVoid(et) ? false : IsSimdType(ValType(et)); +} + static inline jit::MIRType ToMIRType(ExprType et) { @@ -526,6 +665,13 @@ ToCString(ExprType type) case ExprType::F64: return "f64"; case ExprType::AnyRef: return "anyref"; case ExprType::Ref: return "ref"; + case ExprType::I8x16: return "i8x16"; + case ExprType::I16x8: return "i16x8"; + case ExprType::I32x4: return "i32x4"; + case ExprType::F32x4: return "f32x4"; + case ExprType::B8x16: return "b8x16"; + case ExprType::B16x8: return "b16x8"; + case ExprType::B32x4: return "b32x4"; case ExprType::Limit:; } MOZ_CRASH("bad expression type"); @@ -636,6 +782,10 @@ class LitVal uint64_t i64_; float f32_; double f64_; + I8x16 i8x16_; + I16x8 i16x8_; + I32x4 i32x4_; + F32x4 f32x4_; JSObject* ptr_; } u; @@ -654,7 +804,24 @@ class LitVal u.ptr_ = ptr; } + explicit LitVal(const I8x16& i8x16, ValType type = ValType::I8x16) : type_(type) { + MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16); + memcpy(u.i8x16_, i8x16, sizeof(u.i8x16_)); + } + explicit LitVal(const I16x8& i16x8, ValType type = ValType::I16x8) : type_(type) { + MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8); + memcpy(u.i16x8_, i16x8, sizeof(u.i16x8_)); + } + explicit LitVal(const I32x4& i32x4, ValType type = ValType::I32x4) : type_(type) { + MOZ_ASSERT(type_ == ValType::I32x4 || type_ == ValType::B32x4); + memcpy(u.i32x4_, i32x4, sizeof(u.i32x4_)); + } + explicit LitVal(const F32x4& f32x4) : type_(ValType::F32x4) { + memcpy(u.f32x4_, f32x4, sizeof(u.f32x4_)); + } + ValType type() const { return type_; } + bool isSimd() const { return IsSimdType(type()); } static constexpr size_t sizeofLargestValue() { return sizeof(u); } uint32_t i32() const { MOZ_ASSERT(type_ == ValType::I32); return u.i32_; } @@ -662,6 +829,25 @@ class LitVal const float& f32() const { MOZ_ASSERT(type_ == ValType::F32); return u.f32_; } const double& f64() const { MOZ_ASSERT(type_ == ValType::F64); return u.f64_; } JSObject* ptr() const { MOZ_ASSERT(type_.isRefOrAnyRef()); return u.ptr_; } + + const I8x16& i8x16() const { + MOZ_ASSERT(type_ == ValType::I8x16 || type_ == ValType::B8x16); + return u.i8x16_; + } + const I16x8& i16x8() const { + MOZ_ASSERT(type_ == ValType::I16x8 || type_ == ValType::B16x8); + return u.i16x8_; + } + const I32x4& i32x4() const { + MOZ_ASSERT(type_ == ValType::I32x4 || type_ == ValType::B32x4); + return u.i32x4_; + } + const F32x4& f32x4() const { + MOZ_ASSERT(type_ == ValType::F32x4); + return u.f32x4_; + } + // To be used only by Val. + const void* rawSimd() const { return &u.i32x4_; } }; typedef Vector LitValVector; @@ -1294,7 +1480,7 @@ enum class Trap InvalidConversionToInteger, // Integer division by zero. IntegerDivideByZero, - // Out of bounds on wasm memory accesses and asm.js atomic accesses. + // Out of bounds on wasm memory accesses and asm.js SIMD/atomic accesses. OutOfBounds, // Unaligned on wasm atomic accesses; also used for non-standard ARM // unaligned access faults. @@ -1304,6 +1490,10 @@ enum class Trap // call_indirect signature mismatch. IndirectCallBadSig, + // (asm.js only) SIMD float to int conversion failed because the input + // wasn't in bounds. + ImpreciseSimdConversion, + // The internal stack space was exhausted. For compatibility, this throws // the same over-recursed error as JS. StackOverflow, @@ -1446,7 +1636,7 @@ class CodeRange TrapExit, // calls C++ to report and jumps to throw stub DebugTrap, // calls C++ to handle debug event FarJumpIsland, // inserted to connect otherwise out-of-range insns - OutOfBoundsExit, // stub jumped to by non-standard asm.js Atomics + OutOfBoundsExit, // stub jumped to by non-standard asm.js SIMD/Atomics UnalignedExit, // stub jumped to by wasm Atomics and non-standard // ARM unaligned trap Throw // special stack-unwinding stub jumped to by other stubs diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index 45822e217810..4b72930bef17 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -409,6 +409,17 @@ DecodeValType(Decoder& d, ModuleKind kind, uint32_t numTypes, HasGcTypes gcTypes *type = ValType(ValType::Code(uncheckedCode), uncheckedRefTypeIndex); return true; } + case uint8_t(ValType::I8x16): + case uint8_t(ValType::I16x8): + case uint8_t(ValType::I32x4): + case uint8_t(ValType::F32x4): + case uint8_t(ValType::B8x16): + case uint8_t(ValType::B16x8): + case uint8_t(ValType::B32x4): + if (kind != ModuleKind::AsmJS) + return d.fail("bad type"); + *type = ValType(ValType::Code(uncheckedCode)); + return true; default: break; } diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h index ece5d88cf50a..88404cad6221 100644 --- a/js/src/wasm/WasmValidate.h +++ b/js/src/wasm/WasmValidate.h @@ -237,6 +237,18 @@ class Encoder MOZ_MUST_USE bool writeFixedF64(double d) { return write(d); } + MOZ_MUST_USE bool writeFixedI8x16(const I8x16& i8x16) { + return write(i8x16); + } + MOZ_MUST_USE bool writeFixedI16x8(const I16x8& i16x8) { + return write(i16x8); + } + MOZ_MUST_USE bool writeFixedI32x4(const I32x4& i32x4) { + return write(i32x4); + } + MOZ_MUST_USE bool writeFixedF32x4(const F32x4& f32x4) { + return write(f32x4); + } // Variable-length encodings that all use LEB128. @@ -513,6 +525,18 @@ class Decoder MOZ_MUST_USE bool readFixedF64(double* d) { return read(d); } + MOZ_MUST_USE bool readFixedI8x16(I8x16* i8x16) { + return read(i8x16); + } + MOZ_MUST_USE bool readFixedI16x8(I16x8* i16x8) { + return read(i16x8); + } + MOZ_MUST_USE bool readFixedI32x4(I32x4* i32x4) { + return read(i32x4); + } + MOZ_MUST_USE bool readFixedF32x4(F32x4* f32x4) { + return read(f32x4); + } // Variable-length encodings that all use LEB128. @@ -675,6 +699,26 @@ class Decoder ? Op(u8) : Op(uncheckedReadFixedU8() + UINT8_MAX); } + void uncheckedReadFixedI8x16(I8x16* i8x16) { + struct T { I8x16 v; }; + T t = uncheckedRead(); + memcpy(i8x16, &t, sizeof(t)); + } + void uncheckedReadFixedI16x8(I16x8* i16x8) { + struct T { I16x8 v; }; + T t = uncheckedRead(); + memcpy(i16x8, &t, sizeof(t)); + } + void uncheckedReadFixedI32x4(I32x4* i32x4) { + struct T { I32x4 v; }; + T t = uncheckedRead(); + memcpy(i32x4, &t, sizeof(t)); + } + void uncheckedReadFixedF32x4(F32x4* f32x4) { + struct T { F32x4 v; }; + T t = uncheckedRead(); + memcpy(f32x4, &t, sizeof(t)); + } }; // The local entries are part of function bodies and thus serialized by both