| /* |
| * Copyright (C) 2016 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "AtomicsObject.h" |
| |
| #include "JSCInlines.h" |
| #include "JSTypedArrays.h" |
| #include "ObjectPrototype.h" |
| #include "TypedArrayController.h" |
| |
| namespace JSC { |
| |
| STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(AtomicsObject); |
| |
| #define FOR_EACH_ATOMICS_FUNC(macro) \ |
| macro(add, Add, 3) \ |
| macro(and, And, 3) \ |
| macro(compareExchange, CompareExchange, 4) \ |
| macro(exchange, Exchange, 3) \ |
| macro(isLockFree, IsLockFree, 1) \ |
| macro(load, Load, 2) \ |
| macro(or, Or, 3) \ |
| macro(store, Store, 3) \ |
| macro(sub, Sub, 3) \ |
| macro(wait, Wait, 4) \ |
| macro(wake, Wake, 3) \ |
| macro(xor, Xor, 3) |
| |
| #define DECLARE_FUNC_PROTO(lowerName, upperName, count) \ |
| EncodedJSValue JSC_HOST_CALL atomicsFunc ## upperName(ExecState*); |
| FOR_EACH_ATOMICS_FUNC(DECLARE_FUNC_PROTO) |
| #undef DECLARE_FUNC_PROTO |
| |
| const ClassInfo AtomicsObject::s_info = { "Atomics", &Base::s_info, 0, CREATE_METHOD_TABLE(AtomicsObject) }; |
| |
| AtomicsObject::AtomicsObject(VM& vm, Structure* structure) |
| : JSNonFinalObject(vm, structure) |
| { |
| } |
| |
| AtomicsObject* AtomicsObject::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) |
| { |
| AtomicsObject* object = new (NotNull, allocateCell<AtomicsObject>(vm.heap)) AtomicsObject(vm, structure); |
| object->finishCreation(vm, globalObject); |
| return object; |
| } |
| |
| Structure* AtomicsObject::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) |
| { |
| return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); |
| } |
| |
| void AtomicsObject::finishCreation(VM& vm, JSGlobalObject* globalObject) |
| { |
| Base::finishCreation(vm); |
| ASSERT(inherits(info())); |
| |
| #define PUT_DIRECT_NATIVE_FUNC(lowerName, upperName, count) \ |
| putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(&vm, #lowerName), count, atomicsFunc ## upperName, Atomics ## upperName ## Intrinsic, DontEnum); |
| FOR_EACH_ATOMICS_FUNC(PUT_DIRECT_NATIVE_FUNC) |
| #undef PUT_DIRECT_NATIVE_FUNC |
| } |
| |
| namespace { |
| |
| template<unsigned numExtraArgs, typename Adaptor, typename Func> |
| EncodedJSValue atomicOperationWithArgsCase(ExecState* exec, ThrowScope& scope, JSArrayBufferView* typedArrayView, unsigned accessIndex, const Func& func) |
| { |
| JSGenericTypedArrayView<Adaptor>* typedArray = jsCast<JSGenericTypedArrayView<Adaptor>*>(typedArrayView); |
| |
| typename Adaptor::Type extraArgs[numExtraArgs + 1]; // Add 1 to avoid 0 size array error in VS. |
| for (unsigned i = 0; i < numExtraArgs; ++i) { |
| int32_t value = exec->argument(2 + i).toInt32(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| extraArgs[i] = Adaptor::toNativeFromInt32(value); |
| } |
| |
| typename Adaptor::Type result = func(typedArray->typedVector() + accessIndex, extraArgs); |
| return JSValue::encode(Adaptor::toJSValue(result)); |
| } |
| |
| unsigned validatedAccessIndex(VM& vm, ExecState* exec, JSArrayBufferView* typedArrayView) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| JSValue accessIndexValue = exec->argument(1); |
| if (UNLIKELY(!accessIndexValue.isInt32())) { |
| accessIndexValue = jsNumber(accessIndexValue.toNumber(exec)); |
| RETURN_IF_EXCEPTION(scope, 0); |
| if (!accessIndexValue.isInt32()) { |
| throwRangeError(exec, scope, ASCIILiteral("Access index is not an integer.")); |
| return 0; |
| } |
| } |
| int32_t accessIndex = accessIndexValue.asInt32(); |
| |
| ASSERT(typedArrayView->length() <= static_cast<unsigned>(INT_MAX)); |
| if (static_cast<unsigned>(accessIndex) >= typedArrayView->length()) { |
| throwRangeError(exec, scope, ASCIILiteral("Access index out of bounds for atomic access.")); |
| return 0; |
| } |
| |
| return accessIndex; |
| } |
| |
| template<unsigned numExtraArgs, typename Func> |
| EncodedJSValue atomicOperationWithArgs(ExecState* exec, const Func& func) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSValue typedArrayValue = exec->argument(0); |
| if (!typedArrayValue.isCell()) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array argument must be a cell.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSCell* typedArrayCell = typedArrayValue.asCell(); |
| |
| JSType type = typedArrayCell->type(); |
| switch (type) { |
| case Int8ArrayType: |
| case Int16ArrayType: |
| case Int32ArrayType: |
| case Uint8ArrayType: |
| case Uint16ArrayType: |
| case Uint32ArrayType: |
| break; |
| default: |
| throwTypeError(exec, scope, ASCIILiteral("Typed array argument must be an Int8Array, Int16Array, Int32Array, Uint8Array, Uint16Array, or Uint32Array.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSArrayBufferView* typedArrayView = jsCast<JSArrayBufferView*>(typedArrayCell); |
| if (!typedArrayView->isShared()) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array argument must wrap a SharedArrayBuffer.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| unsigned accessIndex = validatedAccessIndex(vm, exec, typedArrayView); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| switch (type) { |
| case Int8ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Int8Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| case Int16ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Int16Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| case Int32ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Int32Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| case Uint8ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Uint8Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| case Uint16ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Uint16Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| case Uint32ArrayType: |
| return atomicOperationWithArgsCase<numExtraArgs, Uint32Adaptor>(exec, scope, typedArrayView, accessIndex, func); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return JSValue::encode(jsUndefined()); |
| } |
| } |
| |
| } // anonymous namespace |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncAdd(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchangeAdd(ptr, args[0]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncAnd(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchangeAnd(ptr, args[0]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncCompareExchange(ExecState* exec) |
| { |
| return atomicOperationWithArgs<2>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicCompareExchangeStrong(ptr, args[0], args[1]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncExchange(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchange(ptr, args[0]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncIsLockFree(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| int32_t size = exec->argument(0).toInt32(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| bool result; |
| switch (size) { |
| case 1: |
| case 2: |
| case 4: |
| result = true; |
| break; |
| default: |
| result = false; |
| break; |
| } |
| return JSValue::encode(jsBoolean(result)); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncLoad(ExecState* exec) |
| { |
| return atomicOperationWithArgs<0>( |
| exec, [&] (auto* ptr, const auto*) { |
| return WTF::atomicLoad(ptr); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncOr(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchangeOr(ptr, args[0]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncStore(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| auto value = args[0]; |
| WTF::atomicStore(ptr, value); |
| return value; |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncSub(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchangeSub(ptr, args[0]); |
| }); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncWait(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSInt32Array* typedArray = jsDynamicCast<JSInt32Array*>(exec->argument(0)); |
| if (!typedArray) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array for wait/wake must be an Int32Array.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| if (!typedArray->isShared()) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array for wait/wake must wrap a SharedArrayBuffer.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| unsigned accessIndex = validatedAccessIndex(vm, exec, typedArray); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| int32_t* ptr = typedArray->typedVector() + accessIndex; |
| |
| int32_t expectedValue = exec->argument(2).toInt32(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| double timeoutInMilliseconds = exec->argument(3).toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| if (!vm.m_typedArrayController->isAtomicsWaitAllowedOnCurrentThread()) { |
| throwTypeError(exec, scope, ASCIILiteral("Atomics.wait cannot be called from the current thread.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| double timeoutInNanoseconds = timeoutInMilliseconds * 1000 * 1000; |
| |
| // This covers the proposed rule: |
| // |
| // 4. If timeout is not provided or is undefined then let t be +inf. Otherwise: |
| // a. Let q be ? ToNumber(timeout). |
| // b. If q is NaN then let t be +inf, otherwise let t be max(0, q). |
| // |
| // exec->argument(3) returns undefined if it's not provided and ToNumber(undefined) returns NaN, |
| // so NaN is the only special case. |
| if (timeoutInNanoseconds == timeoutInNanoseconds) |
| timeoutInNanoseconds = std::max(0., timeoutInNanoseconds); |
| else |
| timeoutInNanoseconds = std::numeric_limits<double>::infinity(); |
| |
| // What happens next is a pile of nonsense, but it's all needed because of corner cases |
| // inside std::chrono. |
| // FIXME: Stop using std::chrono. |
| |
| ParkingLot::Clock::time_point timeout; |
| if (timeoutInNanoseconds > static_cast<double>(std::numeric_limits<int64_t>::max())) |
| timeout = ParkingLot::Clock::time_point::max(); |
| else { |
| std::chrono::nanoseconds relativeTimeout = |
| std::chrono::nanoseconds(static_cast<int64_t>(timeoutInNanoseconds)); |
| if (relativeTimeout < std::chrono::nanoseconds::zero()) |
| timeout = ParkingLot::Clock::now(); |
| else if (relativeTimeout > ParkingLot::Clock::duration::max()) |
| timeout = ParkingLot::Clock::time_point::max(); |
| else { |
| ParkingLot::Clock::duration myRelativeTimeout = |
| std::chrono::duration_cast<ParkingLot::Clock::duration>(relativeTimeout); |
| timeout = ParkingLot::Clock::now() + myRelativeTimeout; |
| } |
| } |
| |
| bool didPassValidation = false; |
| ParkingLot::ParkResult result = ParkingLot::parkConditionally( |
| ptr, |
| [&] () -> bool { |
| didPassValidation = WTF::atomicLoad(ptr) == expectedValue; |
| return didPassValidation; |
| }, |
| [] () { }, |
| timeout); |
| const char* resultString; |
| if (!didPassValidation) |
| resultString = "not-equal"; |
| else if (!result.wasUnparked) |
| resultString = "timed-out"; |
| else |
| resultString = "ok"; |
| return JSValue::encode(jsString(exec, ASCIILiteral(resultString))); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncWake(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSInt32Array* typedArray = jsDynamicCast<JSInt32Array*>(exec->argument(0)); |
| if (!typedArray) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array for wait/wake must be an Int32Array.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| if (!typedArray->isShared()) { |
| throwTypeError(exec, scope, ASCIILiteral("Typed array for wait/wake must wrap a SharedArrayBuffer.")); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| unsigned accessIndex = validatedAccessIndex(vm, exec, typedArray); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| int32_t* ptr = typedArray->typedVector() + accessIndex; |
| |
| JSValue countValue = exec->argument(2); |
| unsigned count = UINT_MAX; |
| if (!countValue.isUndefined()) { |
| int32_t countInt = countValue.toInt32(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| count = std::max(0, countInt); |
| } |
| |
| return JSValue::encode(jsNumber(ParkingLot::unparkCount(ptr, count))); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncXor(ExecState* exec) |
| { |
| return atomicOperationWithArgs<1>( |
| exec, [&] (auto* ptr, const auto* args) { |
| return WTF::atomicExchangeXor(ptr, args[0]); |
| }); |
| } |
| |
| } // namespace JSC |
| |