From e89e482fefca229815fa52ae2a5a853180980144 Mon Sep 17 00:00:00 2001 From: Egor Porsev Date: Mon, 19 Jun 2023 21:17:16 +0300 Subject: [PATCH] implement JS Atomics with mutex Signed-off-by: Egor Porsev --- plugins/ets/runtime/CMakeLists.txt | 2 + plugins/ets/runtime/ets_libbase_runtime.yaml | 104 ++++ .../ets/runtime/intrinsics/std_core_Mutex.cpp | 70 +++ plugins/ets/runtime/types/ets_array.h | 1 + plugins/ets/runtime/types/ets_mutex.cpp | 103 ++++ plugins/ets/runtime/types/ets_mutex.h | 79 +++ plugins/ets/stdlib/escompat/ArrayBuffer.ets | 181 ++++++- plugins/ets/stdlib/escompat/Atomics.ets | 464 ++++++++++++++++++ plugins/ets/stdlib/escompat/Mutex.ets | 58 +++ plugins/ets/stdlib/escompat/TypedArrays.ets | 364 +++++++------- plugins/ets/subproject_sources.gn | 2 + plugins/ets/templates/stdlib/atomics.ets.j2 | 137 ++++++ plugins/ets/templates/stdlib/atomics.sh | 38 ++ .../ets/templates/stdlib/typedArray.ets.j2 | 113 +++-- .../ets/tests/ets_test_suite/CMakeLists.txt | 1 + .../ets_test_suite/atomics/CMakeLists.txt | 50 ++ .../atomics/atomic_increment.ets | 57 +++ .../compare_exchange_nonconcurrent.ets | 32 ++ .../ets_test_suite/atomics/countdownlatch.ets | 66 +++ .../ets_test_suite/atomics/cyclic_barrier.ets | 70 +++ .../atomics/decrement_nonconcurrent.ets | 35 ++ .../atomics/exchange_nonconcurrent.ets | 31 ++ .../atomics/increment_nonconcurrent.ets | 36 ++ .../ets_test_suite/atomics/notify_count.ets | 71 +++ .../ets_test_suite/atomics/notify_wait.ets | 48 ++ .../atomics/rendezvous_channel.ets | 89 ++++ .../atomics/store_load_nonconcurrent.ets | 31 ++ .../ets_test_suite/atomics/wait_notequal.ets | 29 ++ .../ets_test_suite/atomics/wait_timeout.ets | 30 ++ 29 files changed, 2167 insertions(+), 225 deletions(-) create mode 100644 plugins/ets/runtime/intrinsics/std_core_Mutex.cpp create mode 100644 plugins/ets/runtime/types/ets_mutex.cpp create mode 100644 plugins/ets/runtime/types/ets_mutex.h create mode 100644 plugins/ets/stdlib/escompat/Atomics.ets create mode 100644 plugins/ets/stdlib/escompat/Mutex.ets create mode 100644 plugins/ets/templates/stdlib/atomics.ets.j2 create mode 100755 plugins/ets/templates/stdlib/atomics.sh create mode 100644 plugins/ets/tests/ets_test_suite/atomics/CMakeLists.txt create mode 100644 plugins/ets/tests/ets_test_suite/atomics/atomic_increment.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/compare_exchange_nonconcurrent.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/countdownlatch.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/cyclic_barrier.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/decrement_nonconcurrent.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/exchange_nonconcurrent.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/increment_nonconcurrent.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/notify_count.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/notify_wait.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/rendezvous_channel.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/store_load_nonconcurrent.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/wait_notequal.ets create mode 100644 plugins/ets/tests/ets_test_suite/atomics/wait_timeout.ets diff --git a/plugins/ets/runtime/CMakeLists.txt b/plugins/ets/runtime/CMakeLists.txt index 2f8f9fa42..ab948aa57 100644 --- a/plugins/ets/runtime/CMakeLists.txt +++ b/plugins/ets/runtime/CMakeLists.txt @@ -40,6 +40,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/intrinsics/std_core_gc.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_finalization_queue.cpp ${ETS_EXT_SOURCES}/intrinsics/std_core_Promise.cpp + ${ETS_EXT_SOURCES}/intrinsics/std_core_Mutex.cpp ${ETS_EXT_SOURCES}/intrinsics/std_math.cpp ${ETS_EXT_SOURCES}/intrinsics/escompat_JSON.cpp ${ETS_EXT_SOURCES}/intrinsics/helpers/ets_intrinsics_helpers.cpp @@ -55,6 +56,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/types/ets_field.cpp ${ETS_EXT_SOURCES}/types/ets_method.cpp ${ETS_EXT_SOURCES}/types/ets_promise.cpp + ${ETS_EXT_SOURCES}/types/ets_mutex.cpp ${ETS_EXT_SOURCES}/types/ets_object.cpp ${ETS_EXT_SOURCES}/ets_vm_api.cpp ${ETS_EXT_SOURCES}/lambda_utils.cpp diff --git a/plugins/ets/runtime/ets_libbase_runtime.yaml b/plugins/ets/runtime/ets_libbase_runtime.yaml index dd9244083..809e53158 100644 --- a/plugins/ets/runtime/ets_libbase_runtime.yaml +++ b/plugins/ets/runtime/ets_libbase_runtime.yaml @@ -24,6 +24,12 @@ coretypes: - managed_class: Array mirror_class: panda::ets::EtsCharArray +- managed_class: std.core.Mutex + mirror_class: panda::ets::EtsMutex + +- managed_class: std.core.Condition + mirror_class: panda::ets::EtsCondition + intrinsics_namespace: panda::ets::intrinsics intrinsics: @@ -1382,6 +1388,104 @@ intrinsics: args: [] impl: panda::intrinsics::SystemScheduleCoroutine set_flags: [heap_inv] + +###################### +# std.core.Mutex # +###################### + + - name: MutexCreate + space: ets + class_name: escompat.Mutex + method_name: create + static: true + signature: + ret: escompat.Mutex + args: [] + impl: panda::ets::intrinsics::EtsMutexCreate + + - name: MutexLock + space: ets + class_name: escompat.Mutex + method_name: lock + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsMutexLock + + - name: MutexUnlock + space: ets + class_name: escompat.Mutex + method_name: unlock + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsMutexUnlock + + - name: MutexDestroy + space: ets + class_name: escompat.Mutex + method_name: destroyOsMutex + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsMutexDestroyOsMutex + +###################### +# std.core.Condition # +###################### + + - name: ConditionCreate + space: ets + class_name: escompat.Mutex + method_name: createCondition + static: false + signature: + ret: escompat.Condition + args: [] + impl: panda::ets::intrinsics::EtsConditionCreate + + - name: ConditionSignalAll + space: ets + class_name: escompat.Condition + method_name: signalAll + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsConditionSignalAll + + - name: ConditionWait + space: ets + class_name: escompat.Condition + method_name: wait + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsConditionWait + + - name: ConditionTimedWait + space: ets + class_name: escompat.Condition + method_name: timedWait + static: false + signature: + ret: u1 + args: [ i64 ] + impl: panda::ets::intrinsics::EtsConditionTimedWait + + - name: ConditionDestroy + space: ets + class_name: escompat.Condition + method_name: destroyOsCondition + static: false + signature: + ret: void + args: [] + impl: panda::ets::intrinsics::EtsConditionDestroyOsCondition #################### # std.core.Runtime # diff --git a/plugins/ets/runtime/intrinsics/std_core_Mutex.cpp b/plugins/ets/runtime/intrinsics/std_core_Mutex.cpp new file mode 100644 index 000000000..bde86534d --- /dev/null +++ b/plugins/ets/runtime/intrinsics/std_core_Mutex.cpp @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "runtime/include/runtime.h" +#include "runtime/include/panda_vm.h" +#include "runtime/include/object_header.h" +#include "plugins/ets/runtime/types/ets_mutex.h" + +namespace panda::ets::intrinsics { + +extern "C" EtsMutex *EtsMutexCreate() +{ + return EtsMutex::Create(); +} + +extern "C" void EtsMutexLock(EtsMutex *mutex) +{ + mutex->Lock(); +} + +extern "C" void EtsMutexUnlock(EtsMutex *mutex) +{ + mutex->Unlock(); +} + +extern "C" void EtsMutexDestroyOsMutex(EtsMutex *mutex) +{ + delete mutex->GetOsMutex(); +} + +extern "C" EtsCondition *EtsConditionCreate(EtsMutex *mutex) +{ + return mutex->CreateCondition(); +} + +extern "C" void EtsConditionSignalAll(EtsCondition *cond) +{ + cond->SignalAll(); +} + +extern "C" void EtsConditionWait(EtsCondition *cond) +{ + cond->Wait(); +} + +extern "C" bool EtsConditionTimedWait(EtsCondition *cond, int64_t ms) +{ + return cond->TimedWait(ms); +} + +extern "C" void EtsConditionDestroyOsCondition(EtsCondition *cond) +{ + delete cond->GetOsCondition(); +} + +} // namespace panda::ets::intrinsics diff --git a/plugins/ets/runtime/types/ets_array.h b/plugins/ets/runtime/types/ets_array.h index f1b4c35a1..9d6bfb182 100644 --- a/plugins/ets/runtime/types/ets_array.h +++ b/plugins/ets/runtime/types/ets_array.h @@ -21,6 +21,7 @@ #include "plugins/ets/runtime/types/ets_class.h" #include "plugins/ets/runtime/types/ets_primitives.h" #include "plugins/ets/runtime/types/ets_object.h" +#include "plugins/ets/runtime/types/ets_mutex.h" #include "plugins/ets/runtime/ets_class_root.h" #include "plugins/ets/runtime/ets_vm.h" diff --git a/plugins/ets/runtime/types/ets_mutex.cpp b/plugins/ets/runtime/types/ets_mutex.cpp new file mode 100644 index 000000000..610b44c7d --- /dev/null +++ b/plugins/ets/runtime/types/ets_mutex.cpp @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugins/ets/runtime/types/ets_primitives.h" +#include "plugins/ets/runtime/types/ets_mutex.h" +#include "runtime/include/coroutine.h" +#include "runtime/include/mem/allocator.h" +#include "runtime/include/thread.h" +#include "runtime/include/thread_scopes.h" +#include "os/mutex.h" +#include "runtime/include/runtime.h" +#include "runtime/include/panda_vm.h" +#include "runtime/include/object_header.h" +#include "runtime/coroutine_manager.h" +#include "runtime/include/coroutine_events.h" +#include "libpandabase/os/mutex.h" + +namespace panda::ets { + +EtsMutex *EtsMutex::Create() +{ + auto *class_linker = Runtime::GetCurrent()->GetClassLinker(); + ClassLinkerExtension *ext = class_linker->GetExtension(panda_file::SourceLang::ETS); + PandaString descriptor; + auto class_name_bytes = ClassHelper::GetDescriptor(utf::CStringAsMutf8("escompat.Mutex"), &descriptor); + Class *cls = class_linker->GetClass(class_name_bytes, true, ext->GetBootContext()); + auto *obj = reinterpret_cast( + Runtime::GetCurrent()->GetPandaVM()->GetHeapManager()->AllocateNonMovableObject(cls, sizeof(EtsMutex))); + // TODO(egor-porsev): replace with internal allocator once a better method of finalization is implemented + // Currently escompat.Mutex and escompat.Condition are finalized by the FinalizationQueue. + // FinalizationQueue does not guarantee that the finalizers will be invoked at all + obj->mutex_ = reinterpret_cast(new os::memory::Mutex()); + return obj; +} + +EtsCondition *EtsMutex::CreateCondition() +{ + auto *class_linker = Runtime::GetCurrent()->GetClassLinker(); + ClassLinkerExtension *ext = class_linker->GetExtension(panda_file::SourceLang::ETS); + PandaString descriptor; + auto class_name_bytes = ClassHelper::GetDescriptor(utf::CStringAsMutf8("escompat.Condition"), &descriptor); + Class *cls = class_linker->GetClass(class_name_bytes, true, ext->GetBootContext()); + auto *obj = reinterpret_cast( + Runtime::GetCurrent()->GetPandaVM()->GetHeapManager()->AllocateNonMovableObject(cls, sizeof(EtsCondition))); + // TODO(egor-porsev): replace with internal allocator (see above) + obj->cv_ = reinterpret_cast(new os::memory::ConditionVariable()); + obj->etsmutex_ = this; + return obj; +} + +void EtsMutex::Lock() +{ + auto coro = Coroutine::GetCurrent(); + ScopedNativeCodeThread n(coro); + + GetOsMutex()->Lock(); +} + +void EtsMutex::Unlock() +{ + auto coro = Coroutine::GetCurrent(); + ScopedNativeCodeThread n(coro); + + GetOsMutex()->Unlock(); +} + +void EtsCondition::SignalAll() +{ + auto coro = Coroutine::GetCurrent(); + ScopedNativeCodeThread n(coro); + + GetOsCondition()->SignalAll(); +} + +void EtsCondition::Wait() +{ + auto coro = Coroutine::GetCurrent(); + ScopedNativeCodeThread n(coro); + + GetOsCondition()->Wait(etsmutex_->GetOsMutex()); +} + +bool EtsCondition::TimedWait(int64_t ms) +{ + auto coro = Coroutine::GetCurrent(); + ScopedNativeCodeThread n(coro); + + return GetOsCondition()->TimedWait(etsmutex_->GetOsMutex(), ms); +} + +} // namespace panda::ets diff --git a/plugins/ets/runtime/types/ets_mutex.h b/plugins/ets/runtime/types/ets_mutex.h new file mode 100644 index 000000000..5d195ff51 --- /dev/null +++ b/plugins/ets/runtime/types/ets_mutex.h @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_MUTEX_H_ +#define PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_MUTEX_H_ + +#include "mem/object_pointer.h" +#include "os/mutex.h" +#include "runtime/include/object_header.h" +#include "runtime/include/coroutine_events.h" +#include "plugins/ets/runtime/types/ets_primitives.h" + +namespace panda::ets { + +class EtsCondition; + +class EtsMutex : public ObjectHeader { +public: + EtsMutex() = delete; + ~EtsMutex() = delete; + + NO_COPY_SEMANTIC(EtsMutex); + NO_MOVE_SEMANTIC(EtsMutex); + + os::memory::Mutex *GetOsMutex() + { + return reinterpret_cast(mutex_); + } + + static EtsMutex *Create(); + + EtsCondition *CreateCondition(); + + void Lock(); + void Unlock(); + +private: + EtsLong mutex_; +}; + +class EtsCondition : public ObjectHeader { + friend class EtsMutex; + +public: + EtsCondition() = delete; + ~EtsCondition() = delete; + + NO_COPY_SEMANTIC(EtsCondition); + NO_MOVE_SEMANTIC(EtsCondition); + + os::memory::ConditionVariable *GetOsCondition() + { + return reinterpret_cast(cv_); + } + + void SignalAll(); + void Wait(); + bool TimedWait(int64_t ms); + +private: + ObjectPointer etsmutex_; + EtsLong cv_; +}; + +} // namespace panda::ets + +#endif // PANDA_RUNTIME_ETS_FFI_CLASSES_ETS_MUTEX_H_ \ No newline at end of file diff --git a/plugins/ets/stdlib/escompat/ArrayBuffer.ets b/plugins/ets/stdlib/escompat/ArrayBuffer.ets index 00fff7509..58a29a407 100644 --- a/plugins/ets/stdlib/escompat/ArrayBuffer.ets +++ b/plugins/ets/stdlib/escompat/ArrayBuffer.ets @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -60,6 +60,30 @@ export class ArrayBuffer */ public slice(begin: int, end: int): ArrayBuffer { + if (begin < -this.byteLength) { + begin = 0 + } + if (begin < 0) { + begin = this.byteLength + begin + } + if (begin >= this.byteLength) { + begin = this.byteLength + } + + if (end < -this.byteLength) { + end = 0 + } + if (end < 0) { + end = this.byteLength + end + } + if (end >= this.byteLength) { + end = this.byteLength + } + + if (end <= begin) { + return new ArrayBuffer(0) + } + let len = end - begin if (len < 0) { len = 0 @@ -71,6 +95,10 @@ export class ArrayBuffer return res } + public slice(begin: int): ArrayBuffer { + return this.slice(begin, this.byteLength) + } + /** * Returns data at specified index. * No such method in JS library, required for TypedArrays @@ -110,7 +138,158 @@ export class ArrayBuffer this.data = newData } + internal mutexLock(): void { + } + + internal mutexUnlock(): void { + } + private data: byte[] /** Length in bytes */ public readonly byteLength: int } + +export class SharedArrayBuffer extends ArrayBuffer { + private mutex: Mutex + private headWaiter: Waiter | null + + private static mutexFinalizationQueue = new FinalizationQueue(Mutex.finalizeMutex); + + constructor(length: int) { + super(length) + this.mutex = Mutex.create() + + SharedArrayBuffer.mutexFinalizationQueue.register(this, this.mutex) + } + + internal mutexLock(): void { + this.mutex.lock() + } + + internal mutexUnlock(): void { + this.mutex.unlock() + } + + private addWaiter(offset: int): Waiter { + // assert(mutex is held by current thread) + let waiter = new Waiter(this.mutex.createCondition(), offset) + waiter.next = this.headWaiter + this.headWaiter = waiter + return waiter + } + + private removeWaiter(waiter: Waiter): void { + // assert(mutex is held by current thread) + if (waiter.prev != null) { + waiter.prev.next = waiter.next + } + if (waiter.next != null) { + waiter.next.prev = waiter.prev + } + if (waiter == this.headWaiter) { + this.headWaiter = waiter.next + } + } + + internal waitUntilNotified(offset: int): void { + // assert(mutex is held by current thread) + let waiter = this.addWaiter(offset) + + while (!waiter.notified) { + waiter.condition.wait() + Coroutine.Schedule() + } + + this.removeWaiter(waiter) + } + + internal waitUntilNotified(offset: int, timeout: long): boolean { + // assert(mutex is held by current thread) + let waiter = this.addWaiter(offset) + let timedOut: boolean = false + + while (!waiter.notified && !timedOut) { + timedOut = waiter.condition.timedWait(timeout) + } + + this.removeWaiter(waiter) + + return timedOut + } + + internal notify(offset: int): int { + if (offset < 0 || offset >= this.byteLength) { + throw new Error("Out of bounds") + } + + this.mutex.lock() + try { + let waiter = this.headWaiter + let notifiedCount = 0 + while (waiter != null) { + if (waiter.offset == offset) { + notifiedCount += 1 + waiter.notified = true + waiter.condition.signalAll() + } + waiter = waiter.next + } + return notifiedCount + } finally { + this.mutex.unlock() + } + } + + internal notify(offset: int, count: int): int { + if (offset < 0 || offset >= this.byteLength) { + throw new Error("Out of bounds") + } + if (count <= 0) { + return 0 + } + + this.mutex.lock() + try { + let waiter = this.headWaiter + let notifiedCount = 0 + while (waiter != null && notifiedCount < count) { + if (waiter.offset == offset) { + notifiedCount += 1 + waiter.notified = true + waiter.condition.signalAll() + } else { + } + waiter = waiter.next + } + return notifiedCount + } finally { + this.mutex.unlock() + } + } + + public grow(newLength: int) { + throw new Error("not implemented"); + } +} + +class Waiter { + condition: Condition + notified: boolean + offset: int + + prev: Waiter | null + next: Waiter | null + + private static conditionFinalizationQueue = new FinalizationQueue(Condition.finalizeCondition) + + internal constructor(condition: Condition, offset: int) { + this.condition = condition + this.notified = false + this.offset = offset + Waiter.conditionFinalizationQueue.register(this as Object, this.condition as Object) + } + + override toString(): string { + return "Waiter(offset=" + this.offset + ", notified=" + this.notified + ")" + } +} diff --git a/plugins/ets/stdlib/escompat/Atomics.ets b/plugins/ets/stdlib/escompat/Atomics.ets new file mode 100644 index 000000000..36bf61c08 --- /dev/null +++ b/plugins/ets/stdlib/escompat/Atomics.ets @@ -0,0 +1,464 @@ +/* + * Copyright (c) 2021-2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Autogenerated file. See plugins/ets/templates/stdlib/atomics.sh for more details + + +export final class Atomics { + // Int8Array + + public static add(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + let newValue: byte = (typedArray.at(index) + value) as byte + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static and(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) & value) as byte + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static compareExchange(typedArray: Int8Array, index: int, expectedValue: byte, replacementValue: byte): byte throws { + let oldValue: byte + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + if (oldValue == expectedValue) { + typedArray.set(index, replacementValue) + } + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static exchange(typedArray: Int8Array, index: int, value: byte): byte throws { + let oldValue: byte + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static load(typedArray: Int8Array, index: int): byte throws { + let value: byte + typedArray.mutexLock() + try { + value = typedArray.at(index) + } finally { + typedArray.mutexUnlock() + } + return value + } + + public static or(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) | value) as byte + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static store(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + } + + public static sub(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) - value) as byte + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static xor(typedArray: Int8Array, index: int, value: byte): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) ^ value) as byte + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + + + // Int16Array + + public static add(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + let newValue: short = (typedArray.at(index) + value) as short + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static and(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) & value) as short + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static compareExchange(typedArray: Int16Array, index: int, expectedValue: short, replacementValue: short): short throws { + let oldValue: short + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + if (oldValue == expectedValue) { + typedArray.set(index, replacementValue) + } + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static exchange(typedArray: Int16Array, index: int, value: short): short throws { + let oldValue: short + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static load(typedArray: Int16Array, index: int): short throws { + let value: short + typedArray.mutexLock() + try { + value = typedArray.at(index) + } finally { + typedArray.mutexUnlock() + } + return value + } + + public static or(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) | value) as short + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static store(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + } + + public static sub(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) - value) as short + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static xor(typedArray: Int16Array, index: int, value: short): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) ^ value) as short + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + + + // Int32Array + + public static add(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + let newValue: int = (typedArray.at(index) + value) as int + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static and(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) & value) as int + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static compareExchange(typedArray: Int32Array, index: int, expectedValue: int, replacementValue: int): int throws { + let oldValue: int + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + if (oldValue == expectedValue) { + typedArray.set(index, replacementValue) + } + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static exchange(typedArray: Int32Array, index: int, value: int): int throws { + let oldValue: int + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static load(typedArray: Int32Array, index: int): int throws { + let value: int + typedArray.mutexLock() + try { + value = typedArray.at(index) + } finally { + typedArray.mutexUnlock() + } + return value + } + + public static or(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) | value) as int + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static store(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + } + + public static sub(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) - value) as int + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static xor(typedArray: Int32Array, index: int, value: int): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) ^ value) as int + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static notify(typedArray: Int32Array, offset: int): int { + return typedArray.notify(offset) + } + + public static notify(typedArray: Int32Array, offset: int, count: int): int { + return typedArray.notify(offset, count) + } + + public static wait(typedArray: Int32Array, offset: int, value: int): string { + return typedArray.wait(offset, value) + } + + public static wait(typedArray: Int32Array, offset: int, value: int, timeout: long): string { + return typedArray.wait(offset, value, timeout) + } + + public static waitAsync(typedArray: Int32Array, offset: int, value: int): Object { + throw new Error("not implemented") + } + + public static waitAsync(typedArray: Int32Array, offset: int, value: int, timeout: long): Object { + throw new Error("not implemented") + } + + + + public static isLockFree(size: int): boolean { + throw new Error("not implemented") + } + + // BigInt64Array + + public static add(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + let newValue: long = (typedArray.at(index) + value) as long + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static and(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) & value) as long + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static compareExchange(typedArray: BigInt64Array, index: int, expectedValue: long, replacementValue: long): long throws { + let oldValue: long + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + if (oldValue == expectedValue) { + typedArray.set(index, replacementValue) + } + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static exchange(typedArray: BigInt64Array, index: int, value: long): long throws { + let oldValue: long + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static load(typedArray: BigInt64Array, index: int): long throws { + let value: long + typedArray.mutexLock() + try { + value = typedArray.at(index) + } finally { + typedArray.mutexUnlock() + } + return value + } + + public static or(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) | value) as long + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static store(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + } + + public static sub(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) - value) as long + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static xor(typedArray: BigInt64Array, index: int, value: long): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) ^ value) as long + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static wait(typedArray: BigInt64Array, offset: int, value: long): string { + return typedArray.wait(offset, value) + } + + public static wait(typedArray: BigInt64Array, offset: int, value: long, timeout: long): string { + return typedArray.wait(offset, value, timeout) + } + + public static waitAsync(typedArray: BigInt64Array, offset: int, value: long): Object { + throw new Error("not implemented") + } + + public static waitAsync(typedArray: BigInt64Array, offset: int, value: long, timeout: long): Object { + throw new Error("not implemented") + } + + + +} diff --git a/plugins/ets/stdlib/escompat/Mutex.ets b/plugins/ets/stdlib/escompat/Mutex.ets new file mode 100644 index 000000000..1b5595012 --- /dev/null +++ b/plugins/ets/stdlib/escompat/Mutex.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021-2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package escompat; + +export final class Mutex { + private osMutexPtr: long; + + private constructor() {} + + internal static finalizeMutex(object: Object) { + let mutex = object as Mutex + mutex.destroyOsMutex() + } + + internal native destroyOsMutex(): void + + internal static native create(): Mutex + + internal native createCondition(): Condition + + internal native lock(): void + + internal native unlock(): void +} + +export final class Condition { + private mutex: Mutex; + private osCvPtr: long; + + private constructor() {} + + internal static finalizeCondition(object: Object) { + let cond = object as Condition + cond.destroyOsCondition() + } + + internal native destroyOsCondition(): void + + internal native signalAll(): void + + internal native wait(): void + + // returns true if timed out + internal native timedWait(ms: long): boolean +} diff --git a/plugins/ets/stdlib/escompat/TypedArrays.ets b/plugins/ets/stdlib/escompat/TypedArrays.ets index 6a1ad694e..3bcb91a14 100644 --- a/plugins/ets/stdlib/escompat/TypedArrays.ets +++ b/plugins/ets/stdlib/escompat/TypedArrays.ets @@ -20,7 +20,7 @@ package escompat; /** * JS Int8Array API-compatible class */ -export class Int8Array +export final class Int8Array { public static readonly BYTES_PER_ELEMENT = 1 @@ -923,37 +923,8 @@ export class Int8Array */ public slice(begin: int, end: int): Int8Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new Int8Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new Int8Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * Int8Array.BYTES_PER_ELEMENT) - let res = new Int8Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * Int8Array.BYTES_PER_ELEMENT, end * Int8Array.BYTES_PER_ELEMENT) + return new Int8Array(buf) } /** @@ -1179,6 +1150,14 @@ export class Int8Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer @@ -1200,7 +1179,7 @@ export type Uint8Array = Int8Array; /** * JS Int16Array API-compatible class */ -export class Int16Array +export final class Int16Array { public static readonly BYTES_PER_ELEMENT = 2 @@ -2111,37 +2090,8 @@ export class Int16Array */ public slice(begin: int, end: int): Int16Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new Int16Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new Int16Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * Int16Array.BYTES_PER_ELEMENT) - let res = new Int16Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * Int16Array.BYTES_PER_ELEMENT, end * Int16Array.BYTES_PER_ELEMENT) + return new Int16Array(buf) } /** @@ -2367,6 +2317,14 @@ export class Int16Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer @@ -2388,7 +2346,7 @@ export type Uint16Array = Int16Array; /** * JS Int32Array API-compatible class */ -export class Int32Array +export final class Int32Array { public static readonly BYTES_PER_ELEMENT = 4 @@ -3299,37 +3257,8 @@ export class Int32Array */ public slice(begin: int, end: int): Int32Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new Int32Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new Int32Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * Int32Array.BYTES_PER_ELEMENT) - let res = new Int32Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * Int32Array.BYTES_PER_ELEMENT, end * Int32Array.BYTES_PER_ELEMENT) + return new Int32Array(buf) } /** @@ -3555,6 +3484,76 @@ export class Int32Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + + internal notify(offset: int, count: int): int { + if (this.buffer instanceof SharedArrayBuffer) { + let buf = this.buffer as SharedArrayBuffer + return buf.notify(offset * Int32Array.BYTES_PER_ELEMENT, count) + } else { + return 0 + } + } + + internal notify(offset: int): int { + if (this.buffer instanceof SharedArrayBuffer) { + let buf = this.buffer as SharedArrayBuffer + return buf.notify(offset * Int32Array.BYTES_PER_ELEMENT) + } else { + return 0 + } + } + + internal wait(offset: int, value: int): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * Int32Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + b.waitUntilNotified(offset * Int32Array.BYTES_PER_ELEMENT) + return "ok" + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + + internal wait(offset: int, value: int, timeout: long): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * Int32Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + let timedOut = b.waitUntilNotified(offset * Int32Array.BYTES_PER_ELEMENT, timeout) + if (timedOut) { + return "timed-out" + } else { + return "ok" + } + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer @@ -3576,7 +3575,7 @@ export type Uint32Array = Int32Array; /** * JS BigInt64Array API-compatible class */ -export class BigInt64Array +export final class BigInt64Array { public static readonly BYTES_PER_ELEMENT = 8 @@ -4487,37 +4486,8 @@ export class BigInt64Array */ public slice(begin: int, end: int): BigInt64Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new BigInt64Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new BigInt64Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * BigInt64Array.BYTES_PER_ELEMENT) - let res = new BigInt64Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * BigInt64Array.BYTES_PER_ELEMENT, end * BigInt64Array.BYTES_PER_ELEMENT) + return new BigInt64Array(buf) } /** @@ -4743,6 +4713,58 @@ export class BigInt64Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + + internal wait(offset: int, value: long): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * BigInt64Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + b.waitUntilNotified(offset * BigInt64Array.BYTES_PER_ELEMENT) + return "ok" + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + + internal wait(offset: int, value: long, timeout: long): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * BigInt64Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + let timedOut = b.waitUntilNotified(offset * BigInt64Array.BYTES_PER_ELEMENT, timeout) + if (timedOut) { + return "timed-out" + } else { + return "ok" + } + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer @@ -4764,7 +4786,7 @@ export type BigUint64Array = BigInt64Array; /** * JS Float32Array API-compatible class */ -export class Float32Array +export final class Float32Array { public static readonly BYTES_PER_ELEMENT = 4 @@ -5675,37 +5697,8 @@ export class Float32Array */ public slice(begin: int, end: int): Float32Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new Float32Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new Float32Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * Float32Array.BYTES_PER_ELEMENT) - let res = new Float32Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * Float32Array.BYTES_PER_ELEMENT, end * Float32Array.BYTES_PER_ELEMENT) + return new Float32Array(buf) } /** @@ -5931,6 +5924,14 @@ export class Float32Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer @@ -5952,7 +5953,7 @@ export class Float32Array /** * JS Float64Array API-compatible class */ -export class Float64Array +export final class Float64Array { public static readonly BYTES_PER_ELEMENT = 8 @@ -6863,37 +6864,8 @@ export class Float64Array */ public slice(begin: int, end: int): Float64Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new Float64Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new Float64Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * Float64Array.BYTES_PER_ELEMENT) - let res = new Float64Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * Float64Array.BYTES_PER_ELEMENT, end * Float64Array.BYTES_PER_ELEMENT) + return new Float64Array(buf) } /** @@ -7119,6 +7091,14 @@ export class Float64Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer diff --git a/plugins/ets/subproject_sources.gn b/plugins/ets/subproject_sources.gn index 7a855afd2..8370e600f 100644 --- a/plugins/ets/subproject_sources.gn +++ b/plugins/ets/subproject_sources.gn @@ -67,6 +67,7 @@ srcs_runtime = [ "runtime/intrinsics/std_core_Double.cpp", "runtime/intrinsics/std_core_Float.cpp", "runtime/intrinsics/std_core_Promise.cpp", + "runtime/intrinsics/std_core_Mutex.cpp", "runtime/intrinsics/std_core_Runtime.cpp", "runtime/intrinsics/std_core_String.cpp", "runtime/intrinsics/std_core_StringBuilder.cpp", @@ -84,6 +85,7 @@ srcs_runtime = [ "runtime/types/ets_field.cpp", "runtime/types/ets_method.cpp", "runtime/types/ets_promise.cpp", + "runtime/types/ets_mutex.cpp", "runtime/types/ets_object.cpp", "runtime/lambda_utils.cpp", ] diff --git a/plugins/ets/templates/stdlib/atomics.ets.j2 b/plugins/ets/templates/stdlib/atomics.ets.j2 new file mode 100644 index 000000000..8c81f404f --- /dev/null +++ b/plugins/ets/templates/stdlib/atomics.ets.j2 @@ -0,0 +1,137 @@ + // {{N}}Array + + public static add(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + let newValue: {{T}} = (typedArray.at(index) + value) as {{T}} + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static and(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) & value) as {{T}} + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static compareExchange(typedArray: {{N}}Array, index: int, expectedValue: {{T}}, replacementValue: {{T}}): {{T}} throws { + let oldValue: {{T}} + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + if (oldValue == expectedValue) { + typedArray.set(index, replacementValue) + } + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static exchange(typedArray: {{N}}Array, index: int, value: {{T}}): {{T}} throws { + let oldValue: {{T}} + typedArray.mutexLock() + try { + oldValue = typedArray.at(index) + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + return oldValue + } + + public static load(typedArray: {{N}}Array, index: int): {{T}} throws { + let value: {{T}} + typedArray.mutexLock() + try { + value = typedArray.at(index) + } finally { + typedArray.mutexUnlock() + } + return value + } + + public static or(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) | value) as {{T}} + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static store(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + typedArray.set(index, value) + } finally { + typedArray.mutexUnlock() + } + } + + public static sub(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) - value) as {{T}} + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + public static xor(typedArray: {{N}}Array, index: int, value: {{T}}): void throws { + typedArray.mutexLock() + try { + let newValue = (typedArray.at(index) ^ value) as {{T}} + typedArray.set(index, newValue) + } finally { + typedArray.mutexUnlock() + } + } + + {%- if N == "Int32" %} + + public static notify(typedArray: {{N}}Array, offset: int): int { + return typedArray.notify(offset) + } + + public static notify(typedArray: {{N}}Array, offset: int, count: int): int { + return typedArray.notify(offset, count) + } + + {%- endif %} + {%- if N == "Int32" or N == "BigInt64" %} + + public static wait(typedArray: {{N}}Array, offset: int, value: {{T}}): string { + return typedArray.wait(offset, value) + } + + public static wait(typedArray: {{N}}Array, offset: int, value: {{T}}, timeout: long): string { + return typedArray.wait(offset, value, timeout) + } + + public static waitAsync(typedArray: {{N}}Array, offset: int, value: {{T}}): Object { + throw new Error("not implemented") + } + + public static waitAsync(typedArray: {{N}}Array, offset: int, value: {{T}}, timeout: long): Object { + throw new Error("not implemented") + } + + {%- endif %} + + {# TODO: Rewrite. This if statement is here so that the isLockFree method is generated only once #} + {%- if N == "Int32" %} + + public static isLockFree(size: int): boolean { + throw new Error("not implemented") + } + + {%- endif %} diff --git a/plugins/ets/templates/stdlib/atomics.sh b/plugins/ets/templates/stdlib/atomics.sh new file mode 100755 index 000000000..fa120d7ee --- /dev/null +++ b/plugins/ets/templates/stdlib/atomics.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +TYPES=( + "Int8 byte" + "Int16 short" + "Int32 int" + "BigInt64 long" +) + +TEMPLATES=( + atomics.ets.j2 +) + +GENPATH=../../stdlib/escompat + +mkdir $GENPATH 2>/dev/null + +filename=$GENPATH/Atomics.ets +src_abs_path=$(readlink -f $0) +jinja2 -DS=${0##*/} licence.j2 > $filename +echo "" >> $filename + +echo "export final class Atomics {" >> $filename +for atype in "${TYPES[@]}" +do + echo Generating $atype... + read -a strarr <<< "$atype" + + + for template in "${TEMPLATES[@]}" + do + jinja2 -DN=${strarr[0]} -DT=${strarr[1]} $template >> $filename + echo "" >> $filename + done +done +echo "}" >> $filename + +echo Done. diff --git a/plugins/ets/templates/stdlib/typedArray.ets.j2 b/plugins/ets/templates/stdlib/typedArray.ets.j2 index f5cc1b7f9..45a90dca7 100644 --- a/plugins/ets/templates/stdlib/typedArray.ets.j2 +++ b/plugins/ets/templates/stdlib/typedArray.ets.j2 @@ -1,7 +1,7 @@ /** * JS {{N}}Array API-compatible class */ -export class {{N}}Array +export final class {{N}}Array { public static readonly BYTES_PER_ELEMENT = {{S}} @@ -934,37 +934,8 @@ export class {{N}}Array */ public slice(begin: int, end: int): {{N}}Array { - if (begin < -this.length) { - begin = 0 - } - if (begin < 0) { - begin = this.length + begin - } - if (begin >= this.length) { - return new {{N}}Array(new ArrayBuffer(0)) - } - - if (end < -this.length) { - end = 0 - } - if (end < 0) { - end = this.length + end - } - if (end >= this.length) { - end = this.length - } - - if (end < begin) { - return new {{N}}Array(new ArrayBuffer(0)) - } - - let len = end - begin - let resBuf = new ArrayBuffer(len * {{N}}Array.BYTES_PER_ELEMENT) - let res = new {{N}}Array(resBuf) - for (let i = 0; i < len; ++i) { - res.set(i, this.at(i + begin)) - } - return res + let buf = this.buffer.slice(begin * {{N}}Array.BYTES_PER_ELEMENT, end * {{N}}Array.BYTES_PER_ELEMENT) + return new {{N}}Array(buf) } /** @@ -1190,6 +1161,84 @@ export class {{N}}Array return res } + internal mutexLock(): void { + this.buffer.mutexLock() + } + + internal mutexUnlock(): void { + this.buffer.mutexUnlock() + } + + {%- if N == "Int32" %} + + internal notify(offset: int, count: int): int { + if (this.buffer instanceof SharedArrayBuffer) { + let buf = this.buffer as SharedArrayBuffer + return buf.notify(offset * {{N}}Array.BYTES_PER_ELEMENT, count) + } else { + return 0 + } + } + + internal notify(offset: int): int { + if (this.buffer instanceof SharedArrayBuffer) { + let buf = this.buffer as SharedArrayBuffer + return buf.notify(offset * {{N}}Array.BYTES_PER_ELEMENT) + } else { + return 0 + } + } + + {%- endif %} + + {%- if N == "Int32" or N == "BigInt64" %} + + internal wait(offset: int, value: {{T}}): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * {{N}}Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + b.waitUntilNotified(offset * {{N}}Array.BYTES_PER_ELEMENT) + return "ok" + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + + internal wait(offset: int, value: {{T}}, timeout: long): string { + if (this.buffer instanceof SharedArrayBuffer) { + let i = offset * {{N}}Array.BYTES_PER_ELEMENT + let b = this.buffer as SharedArrayBuffer + + b.mutexLock() + try { + if (this.at(offset) != value) { + return "not-equal" + } + let timedOut = b.waitUntilNotified(offset * {{N}}Array.BYTES_PER_ELEMENT, timeout) + if (timedOut) { + return "timed-out" + } else { + return "ok" + } + } finally { + b.mutexUnlock() + } + } else { + throw new Error("Only for shared array buffer") + } + } + + {%- endif %} + /** Underlying ArrayBuffer */ public readonly buffer: ArrayBuffer diff --git a/plugins/ets/tests/ets_test_suite/CMakeLists.txt b/plugins/ets/tests/ets_test_suite/CMakeLists.txt index 69e14b60e..253d1e324 100644 --- a/plugins/ets/tests/ets_test_suite/CMakeLists.txt +++ b/plugins/ets/tests/ets_test_suite/CMakeLists.txt @@ -18,3 +18,4 @@ add_subdirectory(overload) add_subdirectory(lambda) add_subdirectory(gc) add_subdirectory(intrinsics) +add_subdirectory(atomics) diff --git a/plugins/ets/tests/ets_test_suite/atomics/CMakeLists.txt b/plugins/ets/tests/ets_test_suite/atomics/CMakeLists.txt new file mode 100644 index 000000000..830996ff5 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/CMakeLists.txt @@ -0,0 +1,50 @@ +# Copyright (c) 2021-2022 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(atomics_tests + # --- Simple non-concurrent tests + increment_nonconcurrent + decrement_nonconcurrent + store_load_nonconcurrent + exchange_nonconcurrent + compare_exchange_nonconcurrent + # --- Simple synchronization + wait_notequal + notify_wait + wait_timeout + # --- More advanced synchronization + # atomic_increment + countdownlatch + rendezvous_channel + cyclic_barrier +) + +set(tests_in_dir "${CMAKE_CURRENT_SOURCE_DIR}") +set(tests_out_dir "${CMAKE_CURRENT_BINARY_DIR}") + +set(coroutine_options --coroutine-impl=threaded --coroutine-js-mode=false --coroutine-workers-count=10 --gc-type=epsilon) + +add_custom_target(ets_test_suite_atomics) + +foreach(test ${atomics_tests}) + set(test_in "${tests_in_dir}/${test}.ets") + set(test_out_dir "${tests_out_dir}/${test}") + file(MAKE_DIRECTORY ${test_out_dir}) + set(target ets_test_suite_atomics_${test}) + + run_int_jit_aot_ets_code(${test_in} ${test_out_dir} ${target} OPT_LEVEL 0 RUNTIME_EXTRA_OPTIONS ${coroutine_options}) + + if(TARGET ${target}) + add_dependencies(ets_test_suite_atomics ${target}) + endif() +endforeach() diff --git a/plugins/ets/tests/ets_test_suite/atomics/atomic_increment.ets b/plugins/ets/tests/ets_test_suite/atomics/atomic_increment.ets new file mode 100644 index 000000000..4471ceefd --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/atomic_increment.ets @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let EACH = 10000 +let N = 9 // = coroutine-workers - 1 + +let RESULT = (EACH * N) + +function main() { + try { + // The array contains two i32 values: [ i, finished_workers ] + // 'i' in repeatedly incremented + // 'finished_workers' -- number of finished workers + let buf = new SharedArrayBuffer(4 * 2); + let arr = new Int32Array(buf, 0, 2); + + for (let i = 0; i < N; i++) { + launch incrementer(arr) + } + } catch (e) { + // console.println("ERROR: main " + e) + assert(false) + } + // console.println("main finished") +} + +function incrementer(arr: Int32Array): Int { + try { + for (let i = 0; i < EACH; i++) { + Atomics.add(arr, 0, 1) + } + Atomics.add(arr, 1, 1) + let curValue = Atomics.load(arr, 0) + let finishedWorkers = Atomics.load(arr, 1) + // console.println("curValue is " + curValue + ", finished workers: " + finishedWorkers) + if (finishedWorkers == N) { + assert(curValue == RESULT) + } + } catch (e) { + // console.println("ERROR: incrementer " + e) + assert(false) + } + // console.println("incrementer exited") + return 1 +} \ No newline at end of file diff --git a/plugins/ets/tests/ets_test_suite/atomics/compare_exchange_nonconcurrent.ets b/plugins/ets/tests/ets_test_suite/atomics/compare_exchange_nonconcurrent.ets new file mode 100644 index 000000000..1068fb7bd --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/compare_exchange_nonconcurrent.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + for (let i = 1; i < 100; i++) { + let old = Atomics.compareExchange(arr, 0, i - 1, i) + assert(old == i - 1) + } + assert(Atomics.load(arr, 0) == 99) + } catch (e) { + assert(false) + } +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/countdownlatch.ets b/plugins/ets/tests/ets_test_suite/atomics/countdownlatch.ets new file mode 100644 index 000000000..e3e48fd93 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/countdownlatch.ets @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +let N = 9 + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + let c = new Console() // workaround, issue: #12996 + + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + for (let i = 0; i < N; i++) { + launch task() + } + + let cur = Atomics.load(arr, 0) + while (cur != N) { + c.println("waiting, finished tasks: " + cur) + Atomics.wait(arr, 0, cur) + cur = Atomics.load(arr, 0) + } + c.println("all tasks have finished") + } catch (e) { + c.println("ERROR main " + e) + assert(false) + } +} + +function task(): Int { + let c = new Console() // workaround, issue: #12996 + + try { + heavyComputation() + c.println("Task finished. Notifying") + Atomics.add(arr, 0, 1) + Atomics.notify(arr, 0) + } catch (e) { + c.println("ERROR task " + e) + assert(false) + } + return 1 +} + +function heavyComputation(): Int { + let i = 1 + for (let j = 0; j < 10000; j++) { + i += j + } + return i +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/cyclic_barrier.ets b/plugins/ets/tests/ets_test_suite/atomics/cyclic_barrier.ets new file mode 100644 index 000000000..e5d6bc1d2 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/cyclic_barrier.ets @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let s = new SharedArrayBuffer(4); +let arr = new Int32Array(s, 0, 1); + +let N = 5 + +function main() { + setUpBarrier(N) + for (let i = 0; i < N; i++) { + launch task() + } +} + +function task(): Int { + let c = new Console() // workaround, issue: #12996 + + heavyComputation() + c.println("task completed, waiting") + wait() + c.println("wait completed") + return 1 +} + +function setUpBarrier(nTasks: int) { + try { + Atomics.store(arr, 0, nTasks) + } catch (e) { + assert(false) + } +} + +function wait() { + try { + Atomics.sub(arr, 0, 1) + Atomics.notify(arr, 0) + + // wait until is 0 + let cur = Atomics.load(arr, 0) + while (cur != 0) { + Atomics.wait(arr, 0, cur) + cur = Atomics.load(arr, 0) + } + } catch (e) { + assert(false) + } +} + + +function heavyComputation(): Int { + let i = 1 + for (let j = 0; j < 10000; j++) { + i += j + } + return i +} + diff --git a/plugins/ets/tests/ets_test_suite/atomics/decrement_nonconcurrent.ets b/plugins/ets/tests/ets_test_suite/atomics/decrement_nonconcurrent.ets new file mode 100644 index 000000000..fb9ba7cf4 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/decrement_nonconcurrent.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let N = 100 + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + for (let i = 0; i < N; i++) { + Atomics.sub(arr, 0, 1) + } + let x = Atomics.load(arr, 0) + console.println("result is " + x) + assert(x == -N) + } catch (e) { + assert(false) + } +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/exchange_nonconcurrent.ets b/plugins/ets/tests/ets_test_suite/atomics/exchange_nonconcurrent.ets new file mode 100644 index 000000000..1cee677ce --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/exchange_nonconcurrent.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + for (let i = 1; i < 100; i++) { + let old = Atomics.exchange(arr, 0, i) + assert(old == i - 1) + } + } catch (e) { + assert(false) + } +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/increment_nonconcurrent.ets b/plugins/ets/tests/ets_test_suite/atomics/increment_nonconcurrent.ets new file mode 100644 index 000000000..3a0cc8e49 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/increment_nonconcurrent.ets @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let N = 1000 +let RESULT = N + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + for (let i = 0; i < N; i++) { + Atomics.add(arr, 0, 1) + } + let x = Atomics.load(arr, 0) + console.println("result is " + x) + assert(x == RESULT) + } catch (e) { + assert(false) + } +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/notify_count.ets b/plugins/ets/tests/ets_test_suite/atomics/notify_count.ets new file mode 100644 index 000000000..0b6329c39 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/notify_count.ets @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let N = 100 +let COUNT = 5 + +function main() { + let buf: SharedArrayBuffer; + let arr: Int32Array; + + try { + let buf = new SharedArrayBuffer(4 * (1 + N)); + // the 0th value is used for notifications + // i-th value is used to indicate that the i-th waiter has finished (i=1..N) + let arr = new Int32Array(buf, 0, 1 + N); + + for (let i = 0; i < N; i++) { + launch waiter(arr) + } + + Atomics.notify(arr, 0, COUNT) + let count = countWokenUpWaiters(arr) + console.println("Initially, woken up waiters: " + count) + assert(count == COUNT) + + Atomics.notify(arr, 0, N) + count = countWokenUpWaiters(arr) + console.println("Finally, woken up waiters: " + count) + assert(count == N) + } catch (e) { + console.println("ERROR: main " + e) + assert(false) + } + + launch waiter(arr) + launch notifier(arr) +} + +function waiter(typedArray: Int32Array): Int { + let reason = Atomics.wait(typedArray, 0, 0) + console.println("reason is " + reason) + assert(reason == "ok") + return 1 +} + +function countWokenUpWaiters(typedArray: Int32Array): Int { + try { + let count = 0 + for (let i = 1; i <= N; i++) { + if (Atomics.load(typedArray, i) == 1) { + count += 1 + } + } + return count + } catch (e) { + console.println("ERROR: countWokenUpWaiters " + e) + assert(false) + } +} \ No newline at end of file diff --git a/plugins/ets/tests/ets_test_suite/atomics/notify_wait.ets b/plugins/ets/tests/ets_test_suite/atomics/notify_wait.ets new file mode 100644 index 000000000..d04ec315f --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/notify_wait.ets @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function main() { + let buf: SharedArrayBuffer; + let arr: Int32Array; + + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + } catch (e) { + console.println("ERROR: main " + e) + assert(false) + } + + launch waiter(arr) + launch notifier(arr) +} + +function waiter(typedArray: Int32Array): Int { + let reason = Atomics.wait(typedArray, 0, 0) + console.println("reason is " + reason) + assert(reason == "ok" || reason == "not-equal") + return 1 +} + +function notifier(typedArray: Int32Array): Int { + try { + Atomics.store(typedArray, 0, 1) + Atomics.notify(typedArray, 0) + } catch (e) { + console.println("ERROR: notifier " + e) + assert(false) + } + return 1 +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/rendezvous_channel.ets b/plugins/ets/tests/ets_test_suite/atomics/rendezvous_channel.ets new file mode 100644 index 000000000..d41098231 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/rendezvous_channel.ets @@ -0,0 +1,89 @@ +/* +* Copyright (c) 2023 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +let s = new SharedArrayBuffer(4); +let arr = new Int32Array(s, 0, 1); + +function main() { + launch consumer() + heavyComputation() + launch producer() +} + +function producer(): Int { + let c = new Console() // workaround, issue: #12996 + + for (let i = 1; i < 10; i++) { + c.println("sending " + i) + send(i) + c.println("sent " + i) + } + return 1 +} + +function consumer(): Int { + let c = new Console() // workaround, issue: #12996 + + for (let i = 1; i < 10; i++) { + c.println("receiving") + let x = read() + c.println("received " + x) + assert(x == i) + } + return 1 +} + +function send(x: int) { + assert(x != 0) + try { + while (true) { + let oldValue = Atomics.compareExchange(arr, 0, 0, x) + if (oldValue == 0) { + Atomics.notify(arr, 0) + return + } + Atomics.wait(arr, 0, oldValue) + } + } catch (e) { + assert(false) + } +} + +function read(): int { + try { + while (true) { + let value = Atomics.exchange(arr, 0, 0) + // console.println("read value " + value) + if (value != 0) { + Atomics.notify(arr, 0) + return value + } + Atomics.wait(arr, 0, 0) + } + } catch (e) { + assert(false) + } + return -1 +} + + +function heavyComputation(): Int { + let i = 1 + for (let j = 0; j < 10000; j++) { + i += j + } + return i +} + diff --git a/plugins/ets/tests/ets_test_suite/atomics/store_load_nonconcurrent.ets b/plugins/ets/tests/ets_test_suite/atomics/store_load_nonconcurrent.ets new file mode 100644 index 000000000..b93aeef22 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/store_load_nonconcurrent.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +let N = 100 + +let buf: SharedArrayBuffer; +let arr: Int32Array; + +function main() { + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + + Atomics.store(arr, 0, 10); + assert(Atomics.load(arr, 0) == 10) + } catch (e) { + assert(false) + } +} diff --git a/plugins/ets/tests/ets_test_suite/atomics/wait_notequal.ets b/plugins/ets/tests/ets_test_suite/atomics/wait_notequal.ets new file mode 100644 index 000000000..63a309cd6 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/wait_notequal.ets @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function main() { + let buf: SharedArrayBuffer; + let arr: Int32Array; + + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + } catch (e) { + assert(false) + } + + let reason = Atomics.wait(arr, 0, 1) + assert(reason == "not-equal") +} \ No newline at end of file diff --git a/plugins/ets/tests/ets_test_suite/atomics/wait_timeout.ets b/plugins/ets/tests/ets_test_suite/atomics/wait_timeout.ets new file mode 100644 index 000000000..e3c4c1e9e --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/atomics/wait_timeout.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function main() { + let buf: SharedArrayBuffer; + let arr: Int32Array; + + try { + buf = new SharedArrayBuffer(4); + arr = new Int32Array(buf, 0, 1); + } catch (e) { + assert(false) + } + + let reason = Atomics.wait(arr, 0, 0, 10) + console.println("reason is " + reason) + assert(reason == "timed-out") +} \ No newline at end of file -- Gitee