/** * Copyright (c) 2012-2016 André Bargull * Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms. * * <https://github.com/anba/es6draft> */ package com.github.anba.es6draft.runtime.objects.atomics; import static com.github.anba.es6draft.runtime.AbstractOperations.*; import static com.github.anba.es6draft.runtime.internal.Errors.newRangeError; import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError; import static com.github.anba.es6draft.runtime.internal.Properties.createProperties; import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import java.util.function.IntBinaryOperator; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.internal.CompatibilityOption; import com.github.anba.es6draft.runtime.internal.Futex; import com.github.anba.es6draft.runtime.internal.Initializable; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.Properties.Attributes; import com.github.anba.es6draft.runtime.internal.Properties.CompatibilityExtension; import com.github.anba.es6draft.runtime.internal.Properties.Function; import com.github.anba.es6draft.runtime.internal.Properties.Prototype; import com.github.anba.es6draft.runtime.internal.Properties.Value; import com.github.anba.es6draft.runtime.objects.binary.ArrayBuffer; import com.github.anba.es6draft.runtime.objects.binary.ElementType; import com.github.anba.es6draft.runtime.objects.binary.TypedArrayObject; import com.github.anba.es6draft.runtime.types.Intrinsics; import com.github.anba.es6draft.runtime.types.Type; import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject; /** * <h1>Shared Memory and Atomics</h1><br> * <h2>The Atomics Object</h2> * <ul> * <li>Value Properties of the Atomics Object * <li>Runtime semantics * <li>Function Properties of the Atomics Object * </ul> */ public final class AtomicsObject extends OrdinaryObject implements Initializable { /** * Constructs a new Atomics object. * * @param realm * the realm object */ public AtomicsObject(Realm realm) { super(realm); } @Override public void initialize(Realm realm) { createProperties(realm, this, Properties.class); createProperties(realm, this, FenceFunction.class); } /** * Runtime semantics: ValidateSharedIntegerTypedArray( typedArray [, onlyInt32] ) * * @param cx * the execution context * @param typedArray * the typed array object * @return the typed array object */ public static TypedArrayObject ValidateSharedIntegerTypedArray(ExecutionContext cx, Object typedArray) { return ValidateSharedIntegerTypedArray(cx, typedArray, false); } /** * Runtime semantics: ValidateSharedIntegerTypedArray( typedArray [, onlyInt32] ) * * @param cx * the execution context * @param typedArray * the typed array object * @param onlyInt32 * if {@code true} only Int32 typed arrays are accepted * @return the typed array object */ public static TypedArrayObject ValidateSharedIntegerTypedArray(ExecutionContext cx, Object typedArray, boolean onlyInt32) { // FIXME: spec bug - type checks not ordered correctly /* step 1, step 5 */ if (!(typedArray instanceof TypedArrayObject)) { throw newTypeError(cx, Messages.Key.IncompatibleObject); } /* step 2 */ TypedArrayObject array = (TypedArrayObject) typedArray; /* steps 3-4 */ if (onlyInt32) { if (array.getElementType() != ElementType.Int32) { throw newTypeError(cx, Messages.Key.AtomicsInt32ArrayType); } } else { switch (array.getElementType()) { case Int8: case Uint8: case Int16: case Uint16: case Int32: case Uint32: break; default: throw newTypeError(cx, Messages.Key.AtomicsInvalidArrayType); } } /* step 6 */ ArrayBuffer buffer = array.getBuffer(); /* step 7 */ // FIXME: spec bug - type check not needed /* step 8 */ if (!(buffer instanceof SharedArrayBufferObject)) { throw newTypeError(cx, Messages.Key.AtomicsNotSharedBuffer); } /* step 9 */ return array; } /** * Runtime semantics: ValidateAtomicAccess( typedArray, index ) * * @param cx * the execution context * @param typedArray * the typed array object * @param index * the typed array index * @return the typed array index */ public static long ValidateAtomicAccess(ExecutionContext cx, TypedArrayObject typedArray, Object index) { /* step 1 (implicit) */ /* steps 2-9 */ if (Type.isString(index)) { /* steps 2, 5-9 */ long numValue = CanonicalNumericIndexString(Type.stringValue(index).toString()); if (numValue < 0 || numValue >= typedArray.getArrayLength()) { throw newRangeError(cx, Messages.Key.ArrayOffsetOutOfRange); } return numValue; } if (Type.isNumber(index)) { /* steps 3, 5-9 */ double numValue = Type.numberValue(index); if (!IsInteger(numValue) || Double.compare(numValue, -0d) == 0) { throw newRangeError(cx, Messages.Key.ArrayOffsetOutOfRange); } if (numValue < 0 || numValue >= typedArray.getArrayLength()) { throw newRangeError(cx, Messages.Key.ArrayOffsetOutOfRange); } return (long) numValue; } /* step 4 */ throw newRangeError(cx, Messages.Key.AtomicsInvalidArrayIndex); } private static byte getAndAccumulate(TypedArrayObject array, int index, byte value, IntBinaryOperator op) { ByteBuffer buffer = array.getBuffer().getData(); final int byteIndex = index & ~0b11, shift = 8 * (index & 0b11), mask = ~(0xff << shift); int current, newValue, result; do { current = UnsafeHolder.getIntVolatile(buffer, byteIndex); result = 0xff & op.applyAsInt((current >>> shift) & 0xff, value); newValue = (current & mask) | (result << shift); } while (!UnsafeHolder.compareAndSwapInt(buffer, byteIndex, current, newValue)); return (byte) ((current >>> shift) & 0xff); } private static short getAndAccumulate(TypedArrayObject array, int index, short value, IntBinaryOperator op) { ByteBuffer buffer = array.getBuffer().getData(); final int byteIndex = index & ~0b11, shift = 8 * (index & 0b11), mask = ~(0xffff << shift); int current, newValue, result; do { current = UnsafeHolder.getIntVolatile(buffer, byteIndex); result = 0xffff & op.applyAsInt((current >>> shift) & 0xffff, value); newValue = (current & mask) | (result << shift); } while (!UnsafeHolder.compareAndSwapInt(buffer, byteIndex, current, newValue)); return (short) ((current >>> shift) & 0xffff); } private static int getAndAccumulate(TypedArrayObject array, int index, int value, IntBinaryOperator op) { ByteBuffer buffer = array.getBuffer().getData(); int current, newValue; do { current = UnsafeHolder.getIntVolatile(buffer, index); newValue = op.applyAsInt(current, value); } while (!UnsafeHolder.compareAndSwapInt(buffer, index, current, newValue)); return current; } private static byte compareAndSet(TypedArrayObject array, int index, byte expected, byte update) { ByteBuffer buffer = array.getBuffer().getData(); final int byteIndex = index & ~0b11, shift = 8 * (index & 0b11), mask = ~(0xff << shift); int current, actualExpected, actualUpdate; do { current = UnsafeHolder.getIntVolatile(buffer, byteIndex); actualExpected = (current & mask) | (expected << shift); actualUpdate = (current & mask) | (update << shift); if (UnsafeHolder.compareAndSwapInt(buffer, byteIndex, actualExpected, actualUpdate)) { return expected; } } while (current == actualExpected); return (byte) ((current >>> shift) & 0xff); } private static short compareAndSet(TypedArrayObject array, int index, short expected, short update) { ByteBuffer buffer = array.getBuffer().getData(); final int byteIndex = index & ~0b11, shift = 8 * (index & 0b11), mask = ~(0xffff << shift); int current, actualExpected, actualUpdate; do { current = UnsafeHolder.getIntVolatile(buffer, byteIndex); actualExpected = (current & mask) | (expected << shift); actualUpdate = (current & mask) | (update << shift); if (UnsafeHolder.compareAndSwapInt(buffer, byteIndex, actualExpected, actualUpdate)) { return expected; } } while (current == actualExpected); return (short) ((current >>> shift) & 0xffff); } private static int compareAndSet(TypedArrayObject array, int index, int expected, int update) { ByteBuffer buffer = array.getBuffer().getData(); int current; do { if (UnsafeHolder.compareAndSwapInt(buffer, index, expected, update)) { return expected; } current = UnsafeHolder.getIntVolatile(buffer, index); } while (current == expected); return current; } /** * Function Properties of the Atomics Object */ public enum Properties { ; private static double compute(ExecutionContext cx, Object typedArray, Object index, Object value, IntBinaryOperator op) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5-6 */ double v = ToNumber(cx, value); /* steps 7, 9 */ ElementType elementType = array.getElementType(); /* step 8 */ int elementSize = elementType.size(); /* step 10 */ long offset = array.getByteOffset(); /* step 11 */ long indexedPosition = (i * elementSize) + offset; int byteIndex = (int) indexedPosition; assert indexedPosition == byteIndex; /* steps 12-13 */ switch (elementType) { case Int32: return getAndAccumulate(array, byteIndex, ToInt32(v), op); case Int16: return (int) getAndAccumulate(array, byteIndex, ToInt16(v), op); case Int8: return (int) getAndAccumulate(array, byteIndex, ToInt8(v), op); case Uint32: return 0xffff_ffffL & getAndAccumulate(array, byteIndex, ToInt32(v), op); case Uint16: return 0xffff & getAndAccumulate(array, byteIndex, ToInt16(v), op); case Uint8: return 0xff & getAndAccumulate(array, byteIndex, ToInt8(v), op); default: throw new AssertionError(); } } private static int byteBufferIndex(TypedArrayObject array, long index) { return (int) (index * array.getElementType().size() + array.getByteOffset()); } @Prototype public static final Intrinsics __proto__ = Intrinsics.ObjectPrototype; /** * Atomics.OK */ @Value(name = "OK", attributes = @Attributes(writable = false, enumerable = false, configurable = false) ) public static final int OK = 0; /** * Atomics.NOTEQUAL */ @Value(name = "NOTEQUAL", attributes = @Attributes(writable = false, enumerable = false, configurable = false) ) public static final int NOTEQUAL = -1; /** * Atomics.TIMEDOUT */ @Value(name = "TIMEDOUT", attributes = @Attributes(writable = false, enumerable = false, configurable = false) ) public static final int TIMEDOUT = -2; /** * Atomics.add( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "add", arity = 3) public static Object add(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { // FIXME: spec issue - explicitly specify IEEE-754-2008 behaviour for addition? return compute(cx, typedArray, index, value, (r, v) -> r + v); } /** * Atomics.and( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "and", arity = 3) public static Object and(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { // FIXME: spec issue - &-operator not defined return compute(cx, typedArray, index, value, (r, v) -> ToInt32(r) & ToInt32(v)); } /** * Atomics.compareExchange( typedArray, index, expectedValue, replacementValue ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param expectedValue * the expected array value * @param replacementValue * the replacement array value * @return the previous typed array value */ @Function(name = "compareExchange", arity = 4) public static Object compareExchange(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object expectedValue, Object replacementValue) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5-6 */ double expected = ToNumber(cx, expectedValue); /* steps 7-8 */ double replacement = ToNumber(cx, replacementValue); /* steps 9, 13 */ ElementType elementType = array.getElementType(); /* steps 10-11 (moved) */ /* step 12 */ int elementSize = elementType.size(); /* step 14 */ long offset = array.getByteOffset(); /* step 15 */ long indexedPosition = (i * elementSize) + offset; int byteIndex = (int) indexedPosition; assert indexedPosition == byteIndex; /* steps 10-11, 16-17 */ switch (elementType) { case Int32: return compareAndSet(array, byteIndex, ToInt32(expected), ToInt32(replacement)); case Int16: return (int) compareAndSet(array, byteIndex, ToInt16(expected), ToInt16(replacement)); case Int8: return (int) compareAndSet(array, byteIndex, ToInt8(expected), ToInt8(replacement)); case Uint32: return 0xffff_ffffL & compareAndSet(array, byteIndex, ToInt32(expected), ToInt32(replacement)); case Uint16: return 0xffff & compareAndSet(array, byteIndex, ToInt16(expected), ToInt16(replacement)); case Uint8: return 0xff & compareAndSet(array, byteIndex, ToInt8(expected), ToInt8(replacement)); default: throw new AssertionError(); } } /** * Atomics.exchange( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "exchange", arity = 3) public static Object exchange(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { return compute(cx, typedArray, index, value, (r, v) -> v); } /** * Atomics.futexWait( typedArray, index, value, timeout ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @param timeout * the optional timeout value * @return the wait result state * @throws InterruptedException * if interrupted while waiting */ @Function(name = "futexWait", arity = 4) public static Object futexWait(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value, Object timeout) throws InterruptedException { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray, true); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5-6 */ int v = ToInt32(cx, value); /* step 7 */ double t; if (Type.isUndefined(timeout)) { t = Double.POSITIVE_INFINITY; } else { // FIXME: spec bug - apply ToInteger? double q = ToNumber(cx, timeout); t = Double.isNaN(q) ? Double.POSITIVE_INFINITY : Math.max(0, q); } /* steps 8-9 */ // AgentCanSuspend() /* step 10 */ ByteBuffer bufferVal = array.getBuffer().getData(); /* steps 11-14 */ Futex futex = cx.getRuntimeContext().getFutex(); switch (futex.wait(bufferVal, byteBufferIndex(array, i), v, (long) t, TimeUnit.MILLISECONDS)) { case OK: return AtomicsObject.Properties.OK; case NotEqual: return AtomicsObject.Properties.NOTEQUAL; case Timedout: return AtomicsObject.Properties.TIMEDOUT; default: throw new AssertionError(); } } /** * Atomics.futexWake( typedArray, index, count ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param count * the maximum number of workers to wake up * @return the number of woken up workers */ @Function(name = "futexWake", arity = 3) public static Object futexWake(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object count) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray, true); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5-7 */ int c = (int) Math.max(0, ToInteger(cx, count)); /* step 8 */ ByteBuffer bufferVal = array.getBuffer().getData(); /* steps 9-12 */ Futex futex = cx.getRuntimeContext().getFutex(); return futex.wake(bufferVal, byteBufferIndex(array, i), c); } // FIXME: spec bug - parameters order (typedArray, index1, count, value, index2) in SM/V8 /** * Atomics.futexWakeOrRequeue( typedArray, index1, count, index2, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index1 * the first typed array index * @param count * the maximum number of workers to wake up * @param index2 * the second typed array index * @param value * the new typed array value * @return the number of woken up workers */ @Function(name = "futexWakeOrRequeue", arity = 5) public static Object futexWakeOrRequeue(ExecutionContext cx, Object thisValue, Object typedArray, Object index1, Object count, Object index2, Object value) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray, true); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index1); /* steps 5-7 */ int c = (int) Math.max(0, ToInteger(cx, count)); /* steps 8-9 */ long j = ValidateAtomicAccess(cx, array, index2); /* step 10 */ // FIXME: Missing ReturnIfAbrupt int v = ToInt32(cx, value); /* step 11 */ ByteBuffer bufferVal = array.getBuffer().getData(); /* steps 12-15 */ Futex futex = cx.getRuntimeContext().getFutex(); int n = futex.wakeOrRequeue(bufferVal, byteBufferIndex(array, i), c, byteBufferIndex(array, j), v); if (n < 0) { return AtomicsObject.Properties.NOTEQUAL; } return n; } /** * Atomics.isLockFree( size ) * * @param cx * the execution context * @param thisValue * the function this-value * @param size * the size value * @return {@code true} if <var>size</var> bytes can be changed atomically without additional locking */ @Function(name = "isLockFree", arity = 1) public static Object isLockFree(ExecutionContext cx, Object thisValue, Object size) { /* steps 1-2 */ double n = ToInteger(cx, size); /* steps 3-5 */ return (n == 1 || n == 2 || n == 4); } /** * Atomics.load( typedArray, index ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @return the typed array value */ @Function(name = "load", arity = 2) public static Object load(ExecutionContext cx, Object thisValue, Object typedArray, Object index) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5, 7 */ ElementType elementType = array.getElementType(); /* step 6 */ int elementSize = elementType.size(); /* step 8 */ long offset = array.getByteOffset(); /* step 9 */ long indexedPosition = (i * elementSize) + offset; int byteIndex = (int) indexedPosition; assert indexedPosition == byteIndex; /* steps 10-11 */ ByteBuffer buffer = array.getBuffer().getData(); switch (elementType) { case Int32: return UnsafeHolder.getIntVolatile(buffer, byteIndex); case Int16: return (int) UnsafeHolder.getShortVolatile(buffer, byteIndex); case Int8: return (int) UnsafeHolder.getByteVolatile(buffer, byteIndex); case Uint32: return 0xffff_ffffL & UnsafeHolder.getIntVolatile(buffer, byteIndex); case Uint16: return 0xffff & UnsafeHolder.getShortVolatile(buffer, byteIndex); case Uint8: return 0xff & UnsafeHolder.getByteVolatile(buffer, byteIndex); default: throw new AssertionError(); } } /** * Atomics.or( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "or", arity = 3) public static Object or(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { // FIXME: spec issue - |-operator not defined return compute(cx, typedArray, index, value, (r, v) -> ToInt32(r) | ToInt32(v)); } /** * Atomics.store( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the new typed array value */ @Function(name = "store", arity = 3) public static Object store(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { /* steps 1-2 */ TypedArrayObject array = ValidateSharedIntegerTypedArray(cx, typedArray); /* steps 3-4 */ long i = ValidateAtomicAccess(cx, array, index); /* steps 5-6 */ double v = ToNumber(cx, value); /* steps 7, 9 */ ElementType elementType = array.getElementType(); /* step 8 */ int elementSize = elementType.size(); /* step 10 */ long offset = array.getByteOffset(); /* step 11 */ long indexedPosition = (i * elementSize) + offset; int byteIndex = (int) indexedPosition; assert indexedPosition == byteIndex; /* step 12 */ ByteBuffer buffer = array.getBuffer().getData(); switch (elementType) { case Int32: case Uint32: UnsafeHolder.putIntVolatile(buffer, byteIndex, ToInt32(v)); break; case Int16: case Uint16: UnsafeHolder.putShortVolatile(buffer, byteIndex, ToInt16(v)); break; case Int8: case Uint8: UnsafeHolder.putByteVolatile(buffer, byteIndex, ToInt8(v)); break; default: throw new AssertionError(); } /* step 13 */ return v; } /** * Atomics.sub( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "sub", arity = 3) public static Object sub(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { // FIXME: spec issue - explicitly specify IEEE-754-2008 behaviour for subtraction? return compute(cx, typedArray, index, value, (r, v) -> r - v); } /** * Atomics.xor( typedArray, index, value ) * * @param cx * the execution context * @param thisValue * the function this-value * @param typedArray * the typed array object * @param index * the typed array index * @param value * the new typed array value * @return the previous typed array value */ @Function(name = "xor", arity = 3) public static Object xor(ExecutionContext cx, Object thisValue, Object typedArray, Object index, Object value) { // FIXME: spec issue - ^-operator not defined return compute(cx, typedArray, index, value, (r, v) -> ToInt32(r) ^ ToInt32(v)); } } @CompatibilityExtension(CompatibilityOption.AtomicsFence) public enum FenceFunction { ; /** * Atomics.fence( ) * * @param cx * the execution context * @param thisValue * the function this-value * @return the undefined value */ @Function(name = "fence", arity = 0) public static Object fence(ExecutionContext cx, Object thisValue) { UnsafeHolder.fullFence(); return UNDEFINED; } } }