1 | // Copyright (C) 2018 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | #include "qv4arraybuffer_p.h" |
4 | #include "qv4typedarray_p.h" |
5 | #include "qv4atomics_p.h" |
6 | #include "qv4symbol_p.h" |
7 | |
8 | using namespace QV4; |
9 | |
10 | DEFINE_OBJECT_VTABLE(Atomics); |
11 | |
12 | void Heap::Atomics::init() |
13 | { |
14 | Object::init(); |
15 | Scope scope(internalClass->engine); |
16 | ScopedObject m(scope, this); |
17 | |
18 | m->defineDefaultProperty(QStringLiteral("add" ), code: QV4::Atomics::method_add, argumentCount: 3); |
19 | m->defineDefaultProperty(QStringLiteral("and" ), code: QV4::Atomics::method_and, argumentCount: 3); |
20 | m->defineDefaultProperty(QStringLiteral("compareExchange" ), code: QV4::Atomics::method_compareExchange, argumentCount: 4); |
21 | m->defineDefaultProperty(QStringLiteral("exchange" ), code: QV4::Atomics::method_exchange, argumentCount: 3); |
22 | m->defineDefaultProperty(QStringLiteral("isLockFree" ), code: QV4::Atomics::method_isLockFree, argumentCount: 1); |
23 | m->defineDefaultProperty(QStringLiteral("load" ), code: QV4::Atomics::method_load, argumentCount: 2); |
24 | m->defineDefaultProperty(QStringLiteral("or" ), code: QV4::Atomics::method_or, argumentCount: 3); |
25 | m->defineDefaultProperty(QStringLiteral("store" ), code: QV4::Atomics::method_store, argumentCount: 3); |
26 | m->defineDefaultProperty(QStringLiteral("sub" ), code: QV4::Atomics::method_sub, argumentCount: 3); |
27 | m->defineDefaultProperty(QStringLiteral("wait" ), code: QV4::Atomics::method_wait, argumentCount: 4); |
28 | m->defineDefaultProperty(QStringLiteral("wake" ), code: QV4::Atomics::method_wake, argumentCount: 3); |
29 | m->defineDefaultProperty(QStringLiteral("xor" ), code: QV4::Atomics::method_xor, argumentCount: 3); |
30 | |
31 | ScopedString name(scope, scope.engine->newString(QStringLiteral("Atomics" ))); |
32 | m->defineReadonlyConfigurableProperty(name: scope.engine->symbol_toStringTag(), value: name); |
33 | } |
34 | |
35 | static SharedArrayBuffer *validateSharedIntegerTypedArray(Scope &scope, const Value &typedArray, bool onlyInt32 = false) |
36 | { |
37 | const TypedArray *a = typedArray.as<TypedArray>(); |
38 | if (!a) { |
39 | scope.engine->throwTypeError(); |
40 | return nullptr; |
41 | } |
42 | |
43 | TypedArrayType t(a->arrayType()); |
44 | if (!a->d()->type->atomicLoad || (onlyInt32 && t != TypedArrayType::Int32Array)) { |
45 | scope.engine->throwTypeError(); |
46 | return nullptr; |
47 | } |
48 | |
49 | Scoped<SharedArrayBuffer> buffer(scope, a->d()->buffer); |
50 | if (!buffer->isSharedArrayBuffer()) { |
51 | scope.engine->throwTypeError(); |
52 | return nullptr; |
53 | } |
54 | Q_ASSERT(!buffer->hasDetachedArrayData()); |
55 | return buffer; |
56 | } |
57 | |
58 | static int validateAtomicAccess(Scope &scope, const TypedArray &typedArray, const Value &index) |
59 | { |
60 | const TypedArray &a = static_cast<const TypedArray &>(typedArray); |
61 | qint64 idx = index.toIndex(); |
62 | if (scope.hasException()) |
63 | return -1; |
64 | if (idx < 0 || idx >= a.length()) { |
65 | scope.engine->throwRangeError(QStringLiteral("index out of range." )); |
66 | return -1; |
67 | } |
68 | return static_cast<int>(idx); |
69 | } |
70 | |
71 | ReturnedValue atomicReadModifyWrite(const FunctionObject *f, const Value *argv, int argc, AtomicModifyOps modify) |
72 | { |
73 | Scope scope(f); |
74 | if (!argc) |
75 | return scope.engine->throwTypeError(); |
76 | |
77 | SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, typedArray: argv[0]); |
78 | if (!buffer) |
79 | return Encode::undefined(); |
80 | const TypedArray &a = static_cast<const TypedArray &>(argv[0]); |
81 | int index = validateAtomicAccess(scope, typedArray: a, index: argc > 1 ? argv[1] : Value::undefinedValue()); |
82 | if (index < 0) |
83 | return Encode::undefined(); |
84 | |
85 | Value v = Value::fromReturnedValue(val: (argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); |
86 | if (scope.hasException()) |
87 | return Encode::undefined(); |
88 | |
89 | int bytesPerElement = a.d()->type->bytesPerElement; |
90 | int byteOffset = a.d()->byteOffset + index * bytesPerElement; |
91 | |
92 | return a.d()->type->atomicModifyOps[modify](buffer->arrayData() + byteOffset, v); |
93 | } |
94 | |
95 | ReturnedValue Atomics::method_add(const FunctionObject *f, const Value *, const Value *argv, int argc) |
96 | { |
97 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicAdd); |
98 | } |
99 | |
100 | ReturnedValue Atomics::method_and(const FunctionObject *f, const Value *, const Value *argv, int argc) |
101 | { |
102 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicAnd); |
103 | } |
104 | |
105 | ReturnedValue Atomics::method_compareExchange(const FunctionObject *f, const Value *, const Value *argv, int argc) |
106 | { |
107 | Scope scope(f); |
108 | if (!argc) |
109 | return scope.engine->throwTypeError(); |
110 | |
111 | SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, typedArray: argv[0]); |
112 | if (!buffer) |
113 | return Encode::undefined(); |
114 | const TypedArray &a = static_cast<const TypedArray &>(argv[0]); |
115 | int index = validateAtomicAccess(scope, typedArray: a, index: argc > 1 ? argv[1] : Value::undefinedValue()); |
116 | if (index < 0) |
117 | return Encode::undefined(); |
118 | |
119 | Value expected = Value::fromReturnedValue(val: (argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); |
120 | if (scope.hasException()) |
121 | return Encode::undefined(); |
122 | Value v = Value::fromReturnedValue(val: (argc > 3 ? argv[3] : Value::undefinedValue()).convertedToNumber()); |
123 | if (scope.hasException()) |
124 | return Encode::undefined(); |
125 | |
126 | int bytesPerElement = a.d()->type->bytesPerElement; |
127 | int byteOffset = a.d()->byteOffset + index * bytesPerElement; |
128 | |
129 | return a.d()->type->atomicCompareExchange(buffer->arrayData() + byteOffset, expected, v); |
130 | } |
131 | |
132 | ReturnedValue Atomics::method_exchange(const FunctionObject *f, const Value *, const Value *argv, int argc) |
133 | { |
134 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicExchange); |
135 | } |
136 | |
137 | ReturnedValue Atomics::method_isLockFree(const FunctionObject *, const Value *, const Value *argv, int argc) |
138 | { |
139 | if (!argc) |
140 | return Encode(false); |
141 | double n = argv[0].toInteger(); |
142 | if (n == 4.) |
143 | return Encode(true); |
144 | if (n == 2.) |
145 | return Encode(QAtomicOps<unsigned short>::isTestAndSetNative()); |
146 | #ifdef Q_ATOMIC_INT8_IS_SUPPORTED |
147 | if (n == 1.) |
148 | return Encode(QAtomicOps<unsigned char>::isTestAndSetNative()); |
149 | #endif |
150 | return Encode(false); |
151 | } |
152 | |
153 | ReturnedValue Atomics::method_load(const FunctionObject *f, const Value *, const Value *argv, int argc) |
154 | { |
155 | Scope scope(f); |
156 | if (!argc) |
157 | return scope.engine->throwTypeError(); |
158 | |
159 | SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, typedArray: argv[0]); |
160 | if (!buffer) |
161 | return Encode::undefined(); |
162 | const TypedArray &a = static_cast<const TypedArray &>(argv[0]); |
163 | int index = validateAtomicAccess(scope, typedArray: a, index: argc > 1 ? argv[1] : Value::undefinedValue()); |
164 | if (index < 0) |
165 | return Encode::undefined(); |
166 | |
167 | int bytesPerElement = a.d()->type->bytesPerElement; |
168 | int byteOffset = a.d()->byteOffset + index * bytesPerElement; |
169 | |
170 | return a.d()->type->atomicLoad(buffer->arrayData() + byteOffset); |
171 | } |
172 | |
173 | ReturnedValue Atomics::method_or(const FunctionObject *f, const Value *, const Value *argv, int argc) |
174 | { |
175 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicOr); |
176 | } |
177 | |
178 | ReturnedValue Atomics::method_store(const FunctionObject *f, const Value *, const Value *argv, int argc) |
179 | { |
180 | Scope scope(f); |
181 | if (!argc) |
182 | return scope.engine->throwTypeError(); |
183 | |
184 | SharedArrayBuffer *buffer = validateSharedIntegerTypedArray(scope, typedArray: argv[0]); |
185 | if (!buffer) |
186 | return Encode::undefined(); |
187 | const TypedArray &a = static_cast<const TypedArray &>(argv[0]); |
188 | int index = validateAtomicAccess(scope, typedArray: a, index: argc > 1 ? argv[1] : Value::undefinedValue()); |
189 | if (index < 0) |
190 | return Encode::undefined(); |
191 | |
192 | Value v = Value::fromReturnedValue(val: (argc > 2 ? argv[2] : Value::undefinedValue()).convertedToNumber()); |
193 | if (scope.hasException()) |
194 | return Encode::undefined(); |
195 | |
196 | int bytesPerElement = a.d()->type->bytesPerElement; |
197 | int byteOffset = a.d()->byteOffset + index * bytesPerElement; |
198 | |
199 | return a.d()->type->atomicStore(buffer->arrayData() + byteOffset, v); |
200 | } |
201 | |
202 | ReturnedValue Atomics::method_sub(const FunctionObject *f, const Value *, const Value *argv, int argc) |
203 | { |
204 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicSub); |
205 | } |
206 | |
207 | ReturnedValue Atomics::method_wait(const FunctionObject *f, const Value *, const Value *, int) |
208 | { |
209 | return f->engine()->throwTypeError(); |
210 | } |
211 | |
212 | ReturnedValue Atomics::method_wake(const FunctionObject *f, const Value *, const Value *, int) |
213 | { |
214 | return f->engine()->throwTypeError(); |
215 | } |
216 | |
217 | ReturnedValue Atomics::method_xor(const FunctionObject *f, const Value *, const Value *argv, int argc) |
218 | { |
219 | return atomicReadModifyWrite(f, argv, argc, modify: AtomicXor); |
220 | |
221 | } |
222 | |