blob: ca7ed730c765d20c79d180b0ec7c8be335ee0fbf [file] [log] [blame]
/*
* 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