/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "mozilla/Alignment.h" #include "mozilla/Assertions.h" #include "jit/AtomicOperations.h" #include "jsapi-tests/tests.h" #include "vm/ArrayBufferObject.h" #include "vm/SharedMem.h" #include "vm/Uint8Clamped.h" #include "wasm/WasmJS.h" using namespace js; // Machinery to disguise pointer addresses to the C++ compiler -- quite possibly // not thread-safe. extern void setHiddenPointer(void* p); extern void* getHiddenPointer(); void* hidePointerValue(void* p) { setHiddenPointer(p); return getHiddenPointer(); } ////////////////////////////////////////////////////////////////////// // // Lock-freedom predicates BEGIN_REUSABLE_TEST(testAtomicLockFree8) { // isLockfree8() must not return true if there are no 8-byte atomics CHECK(!jit::AtomicOperations::isLockfree8() || jit::AtomicOperations::hasAtomic8()); // We must have lock-free 8-byte atomics on every platform where we support // wasm, but we don't care otherwise. CHECK(!wasm::HasSupport(cx) || jit::AtomicOperations::isLockfree8()); return true; } END_TEST(testAtomicLockFree8) // The JS spec requires specific behavior for all but 1 and 2. BEGIN_REUSABLE_TEST(testAtomicLockFreeJS) { static_assert(jit::AtomicOperations::isLockfreeJS(1) == true); // false is allowed by spec but not in SpiderMonkey static_assert(jit::AtomicOperations::isLockfreeJS(2) == true); // ditto static_assert(jit::AtomicOperations::isLockfreeJS(8) == true); // ditto static_assert(jit::AtomicOperations::isLockfreeJS(3) == false); // required static_assert(jit::AtomicOperations::isLockfreeJS(4) == true); // required static_assert(jit::AtomicOperations::isLockfreeJS(5) == false); // required static_assert(jit::AtomicOperations::isLockfreeJS(6) == false); // required static_assert(jit::AtomicOperations::isLockfreeJS(7) == false); // required return true; } END_TEST(testAtomicLockFreeJS) ////////////////////////////////////////////////////////////////////// // // Fence // This only tests that fenceSeqCst is defined and that it doesn't crash if we // call it, but it has no return value and its effect is not observable here. BEGIN_REUSABLE_TEST(testAtomicFence) { jit::AtomicOperations::fenceSeqCst(); return true; } END_TEST(testAtomicFence) ////////////////////////////////////////////////////////////////////// // // Memory access primitives // These tests for the atomic load and store primitives ascertain that the // primitives are defined and that they load and store the values they should, // but not that the primitives are actually atomic wrt to the memory subsystem. // Memory for testing atomics. This must be aligned to the natural alignment of // the type we're testing; for now, use 8-byte alignment for all. MOZ_ALIGNED_DECL(8, static uint8_t atomicMem[8]); MOZ_ALIGNED_DECL(8, static uint8_t atomicMem2[8]); // T is the primitive type we're testing, and A and B are references to constant // bindings holding values of that type. // // No bytes of A and B should be 0 or FF. A+B and A-B must not overflow. #define ATOMIC_TESTS(T, A, B) \ T* q = (T*)hidePointerValue((void*)atomicMem); \ *q = A; \ SharedMem p = \ SharedMem::shared((T*)hidePointerValue((T*)atomicMem)); \ CHECK(*q == A); \ CHECK(jit::AtomicOperations::loadSeqCst(p) == A); \ CHECK(*q == A); \ jit::AtomicOperations::storeSeqCst(p, B); \ CHECK(*q == B); \ CHECK(jit::AtomicOperations::exchangeSeqCst(p, A) == B); \ CHECK(*q == A); \ CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, (T)0, (T)1) == \ A); /*failure*/ \ CHECK(*q == A); \ CHECK(jit::AtomicOperations::compareExchangeSeqCst(p, A, B) == \ A); /*success*/ \ CHECK(*q == B); \ *q = A; \ CHECK(jit::AtomicOperations::fetchAddSeqCst(p, B) == A); \ CHECK(*q == A + B); \ *q = A; \ CHECK(jit::AtomicOperations::fetchSubSeqCst(p, B) == A); \ CHECK(*q == A - B); \ *q = A; \ CHECK(jit::AtomicOperations::fetchAndSeqCst(p, B) == A); \ CHECK(*q == (A & B)); \ *q = A; \ CHECK(jit::AtomicOperations::fetchOrSeqCst(p, B) == A); \ CHECK(*q == (A | B)); \ *q = A; \ CHECK(jit::AtomicOperations::fetchXorSeqCst(p, B) == A); \ CHECK(*q == (A ^ B)); \ *q = A; \ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \ jit::AtomicOperations::storeSafeWhenRacy(p, B); \ CHECK(*q == B); \ T* q2 = (T*)hidePointerValue((void*)atomicMem2); \ SharedMem p2 = \ SharedMem::shared((T*)hidePointerValue((void*)atomicMem2)); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1); \ CHECK(*q2 == A); \ return true BEGIN_REUSABLE_TEST(testAtomicOperationsU8) { const uint8_t A = 0xab; const uint8_t B = 0x37; ATOMIC_TESTS(uint8_t, A, B); } END_TEST(testAtomicOperationsU8) BEGIN_REUSABLE_TEST(testAtomicOperationsI8) { const int8_t A = 0x3b; const int8_t B = 0x27; ATOMIC_TESTS(int8_t, A, B); } END_TEST(testAtomicOperationsI8) BEGIN_REUSABLE_TEST(testAtomicOperationsU16) { const uint16_t A = 0xabdc; const uint16_t B = 0x3789; ATOMIC_TESTS(uint16_t, A, B); } END_TEST(testAtomicOperationsU16) BEGIN_REUSABLE_TEST(testAtomicOperationsI16) { const int16_t A = 0x3bdc; const int16_t B = 0x2737; ATOMIC_TESTS(int16_t, A, B); } END_TEST(testAtomicOperationsI16) BEGIN_REUSABLE_TEST(testAtomicOperationsU32) { const uint32_t A = 0xabdc0588; const uint32_t B = 0x37891942; ATOMIC_TESTS(uint32_t, A, B); } END_TEST(testAtomicOperationsU32) BEGIN_REUSABLE_TEST(testAtomicOperationsI32) { const int32_t A = 0x3bdc0588; const int32_t B = 0x27371843; ATOMIC_TESTS(int32_t, A, B); } END_TEST(testAtomicOperationsI32) BEGIN_REUSABLE_TEST(testAtomicOperationsU64) { if (!jit::AtomicOperations::hasAtomic8()) { return true; } const uint64_t A(0x9aadf00ddeadbeef); const uint64_t B(0x4eedbead1337f001); ATOMIC_TESTS(uint64_t, A, B); } END_TEST(testAtomicOperationsU64) BEGIN_REUSABLE_TEST(testAtomicOperationsI64) { if (!jit::AtomicOperations::hasAtomic8()) { return true; } const int64_t A(0x2aadf00ddeadbeef); const int64_t B(0x4eedbead1337f001); ATOMIC_TESTS(int64_t, A, B); } END_TEST(testAtomicOperationsI64) // T is the primitive float type we're testing, and A and B are references to // constant bindings holding values of that type. // // Stay away from 0, NaN, infinities, and denormals. #define ATOMIC_FLOAT_TESTS(T, A, B) \ T* q = (T*)hidePointerValue((void*)atomicMem); \ *q = A; \ SharedMem p = \ SharedMem::shared((T*)hidePointerValue((T*)atomicMem)); \ CHECK(*q == A); \ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \ jit::AtomicOperations::storeSafeWhenRacy(p, B); \ CHECK(*q == B); \ T* q2 = (T*)hidePointerValue((void*)atomicMem2); \ SharedMem p2 = \ SharedMem::shared((T*)hidePointerValue((void*)atomicMem2)); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2, p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2, p.unwrap(), sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memcpySafeWhenRacy(p2.unwrap(), p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::memmoveSafeWhenRacy(p2, p, sizeof(T)); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::podCopySafeWhenRacy(p2, p, 1); \ CHECK(*q2 == A); \ *q = A; \ *q2 = B; \ jit::AtomicOperations::podMoveSafeWhenRacy(p2, p, 1); \ CHECK(*q2 == A); \ return true BEGIN_REUSABLE_TEST(testAtomicOperationsF32) { const float A(123.25); const float B(-987.75); ATOMIC_FLOAT_TESTS(float, A, B); } END_TEST(testAtomicOperationsF32) BEGIN_REUSABLE_TEST(testAtomicOperationsF64) { const double A(123.25); const double B(-987.75); ATOMIC_FLOAT_TESTS(double, A, B); } END_TEST(testAtomicOperationsF64) #define ATOMIC_CLAMPED_TESTS(T, A, B) \ T* q = (T*)hidePointerValue((void*)atomicMem); \ *q = A; \ SharedMem p = \ SharedMem::shared((T*)hidePointerValue((T*)atomicMem)); \ CHECK(*q == A); \ CHECK(jit::AtomicOperations::loadSafeWhenRacy(p) == A); \ jit::AtomicOperations::storeSafeWhenRacy(p, B); \ CHECK(*q == B); \ return true BEGIN_REUSABLE_TEST(testAtomicOperationsU8Clamped) { const uint8_clamped A(0xab); const uint8_clamped B(0x37); ATOMIC_CLAMPED_TESTS(uint8_clamped, A, B); } END_TEST(testAtomicOperationsU8Clamped) #undef ATOMIC_TESTS #undef ATOMIC_FLOAT_TESTS #undef ATOMIC_CLAMPED_TESTS