| /* |
| * Copyright (C) 2016-2017 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 "FrameTracers.h" |
| #include "JSCInlines.h" |
| #include "JSTypedArrays.h" |
| #include "ObjectPrototype.h" |
| #include "ReleaseHeapAccessScope.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, nullptr, nullptr, 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(vm, info())); |
| |
| #define PUT_DIRECT_NATIVE_FUNC(lowerName, upperName, count) \ |
| putDirectNativeFunctionWithoutTransition(vm, globalObject, Identifier::fromString(&vm, #lowerName), count, atomicsFunc ## upperName, Atomics ## upperName ## Intrinsic, static_cast<unsigned>(PropertyAttribute::DontEnum)); |
| FOR_EACH_ATOMICS_FUNC(PUT_DIRECT_NATIVE_FUNC) |
| #undef PUT_DIRECT_NATIVE_FUNC |
| } |
| |
| namespace { |
| |
| template<typename Adaptor, typename Func> |
| EncodedJSValue atomicOperationWithArgsCase(ExecState* exec, const JSValue* args, ThrowScope& scope, JSArrayBufferView* typedArrayView, unsigned accessIndex, const Func& func) |
| { |
| JSGenericTypedArrayView<Adaptor>* typedArray = jsCast<JSGenericTypedArrayView<Adaptor>*>(typedArrayView); |
| |
| double extraArgs[Func::numExtraArgs + 1]; // Add 1 to avoid 0 size array error in VS. |
| for (unsigned i = 0; i < Func::numExtraArgs; ++i) { |
| double value = args[2 + i].toInteger(exec); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| extraArgs[i] = value; |
| } |
| |
| return JSValue::encode(func(typedArray->typedVector() + accessIndex, extraArgs)); |
| } |
| |
| unsigned validatedAccessIndex(VM& vm, ExecState* exec, JSValue accessIndexValue, JSArrayBufferView* typedArrayView) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| if (UNLIKELY(!accessIndexValue.isInt32())) { |
| double accessIndexDouble = accessIndexValue.toNumber(exec); |
| RETURN_IF_EXCEPTION(scope, 0); |
| if (accessIndexDouble == 0) |
| accessIndexValue = jsNumber(0); |
| else { |
| accessIndexValue = jsNumber(accessIndexDouble); |
| 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<typename Func> |
| EncodedJSValue atomicOperationWithArgs(VM& vm, ExecState* exec, const JSValue* args, const Func& func) |
| { |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSValue typedArrayValue = args[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, args[1], typedArrayView); |
| RETURN_IF_EXCEPTION(scope, JSValue::encode(jsUndefined())); |
| |
| switch (type) { |
| case Int8ArrayType: |
| return atomicOperationWithArgsCase<Int8Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| case Int16ArrayType: |
| return atomicOperationWithArgsCase<Int16Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| case Int32ArrayType: |
| return atomicOperationWithArgsCase<Int32Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| case Uint8ArrayType: |
| return atomicOperationWithArgsCase<Uint8Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| case Uint16ArrayType: |
| return atomicOperationWithArgsCase<Uint16Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| case Uint32ArrayType: |
| return atomicOperationWithArgsCase<Uint32Adaptor>(exec, args, scope, typedArrayView, accessIndex, func); |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| return JSValue::encode(jsUndefined()); |
| } |
| } |
| |
| template<typename Func> |
| EncodedJSValue atomicOperationWithArgs(ExecState* exec, const Func& func) |
| { |
| JSValue args[2 + Func::numExtraArgs]; |
| for (unsigned i = 2 + Func::numExtraArgs; i--;) |
| args[i] = exec->argument(i); |
| return atomicOperationWithArgs(exec->vm(), exec, args, func); |
| } |
| |
| struct AddFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchangeAdd(ptr, toInt32(args[0]))); |
| } |
| }; |
| |
| struct AndFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchangeAnd(ptr, toInt32(args[0]))); |
| } |
| }; |
| |
| struct CompareExchangeFunc { |
| static const unsigned numExtraArgs = 2; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| T expected = static_cast<T>(toInt32(args[0])); |
| T newValue = static_cast<T>(toInt32(args[1])); |
| return jsNumber(WTF::atomicCompareExchangeStrong(ptr, expected, newValue)); |
| } |
| }; |
| |
| struct ExchangeFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchange(ptr, static_cast<T>(toInt32(args[0])))); |
| } |
| }; |
| |
| struct LoadFunc { |
| static const unsigned numExtraArgs = 0; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double*) const |
| { |
| return jsNumber(WTF::atomicLoadFullyFenced(ptr)); |
| } |
| }; |
| |
| struct OrFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchangeOr(ptr, toInt32(args[0]))); |
| } |
| }; |
| |
| struct StoreFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| double valueAsInt = args[0]; |
| T valueAsT = static_cast<T>(toInt32(valueAsInt)); |
| WTF::atomicStoreFullyFenced(ptr, valueAsT); |
| return jsNumber(valueAsInt); |
| } |
| }; |
| |
| struct SubFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchangeSub(ptr, toInt32(args[0]))); |
| } |
| }; |
| |
| struct XorFunc { |
| static const unsigned numExtraArgs = 1; |
| |
| template<typename T> |
| JSValue operator()(T* ptr, const double* args) const |
| { |
| return jsNumber(WTF::atomicExchangeXor(ptr, toInt32(args[0]))); |
| } |
| }; |
| |
| EncodedJSValue isLockFree(ExecState* exec, JSValue arg) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| int32_t size = arg.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)); |
| } |
| |
| } // anonymous namespace |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncAdd(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, AddFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncAnd(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, AndFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncCompareExchange(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, CompareExchangeFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncExchange(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, ExchangeFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncIsLockFree(ExecState* exec) |
| { |
| return isLockFree(exec, exec->argument(0)); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncLoad(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, LoadFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncOr(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, OrFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncStore(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, StoreFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncSub(ExecState* exec) |
| { |
| return atomicOperationWithArgs(exec, SubFunc()); |
| } |
| |
| EncodedJSValue JSC_HOST_CALL atomicsFuncWait(ExecState* exec) |
| { |
| VM& vm = exec->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSInt32Array* typedArray = jsDynamicCast<JSInt32Array*>(vm, 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, exec->argument(1), 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()); |
| } |
| |
| Seconds timeout = Seconds::fromMilliseconds(timeoutInMilliseconds); |
| |
| // 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 (!std::isnan(timeout)) |
| timeout = std::max(0_s, timeout); |
| else |
| timeout = Seconds::infinity(); |
| |
| bool didPassValidation = false; |
| ParkingLot::ParkResult result; |
| { |
| ReleaseHeapAccessScope releaseHeapAccessScope(vm.heap); |
| result = ParkingLot::parkConditionally( |
| ptr, |
| [&] () -> bool { |
| didPassValidation = WTF::atomicLoad(ptr) == expectedValue; |
| return didPassValidation; |
| }, |
| [] () { }, |
| MonotonicTime::now() + 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*>(vm, 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, exec->argument(1), 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(exec, XorFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsAdd(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, AddFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsAnd(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, AndFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsCompareExchange(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue expected, EncodedJSValue newValue) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(expected), JSValue::decode(newValue)}; |
| return atomicOperationWithArgs(vm, exec, args, CompareExchangeFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsExchange(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, ExchangeFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsIsLockFree(ExecState* exec, EncodedJSValue size) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| return isLockFree(exec, JSValue::decode(size)); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsLoad(ExecState* exec, EncodedJSValue base, EncodedJSValue index) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index)}; |
| return atomicOperationWithArgs(vm, exec, args, LoadFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsOr(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, OrFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsStore(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, StoreFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsSub(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, SubFunc()); |
| } |
| |
| EncodedJSValue JIT_OPERATION operationAtomicsXor(ExecState* exec, EncodedJSValue base, EncodedJSValue index, EncodedJSValue operand) |
| { |
| VM& vm = exec->vm(); |
| NativeCallFrameTracer tracer(&vm, exec); |
| JSValue args[] = {JSValue::decode(base), JSValue::decode(index), JSValue::decode(operand)}; |
| return atomicOperationWithArgs(vm, exec, args, XorFunc()); |
| } |
| |
| } // namespace JSC |
| |