From 755b83c01ae82184290fe8a904de257d5f01a5c6 Mon Sep 17 00:00:00 2001 From: Jonne Ransijn Date: Sat, 2 Nov 2024 00:09:07 +0100 Subject: [PATCH] LibJS: Implement `tc39/proposal-atomics-microwait` (Atomics.pause) Implements the https://github.com/tc39/proposal-atomics-microwait proposal which has recently hit Stage 3. This commit passes all relevant tests in `test262`. --- AK/Atomic.h | 9 +++++ .../Libraries/LibJS/Runtime/AtomicsObject.cpp | 35 +++++++++++++++++++ .../Libraries/LibJS/Runtime/AtomicsObject.h | 1 + .../LibJS/Runtime/CommonPropertyNames.h | 1 + Userland/Libraries/LibJS/Runtime/ErrorTypes.h | 1 + Userland/Libraries/LibJS/Runtime/Value.h | 13 +++++++ .../Tests/builtins/Atomics/Atomics.pause.js | 24 +++++++++++++ 7 files changed, 84 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.pause.js diff --git a/AK/Atomic.h b/AK/Atomic.h index 3896c194159..c9e1203e499 100644 --- a/AK/Atomic.h +++ b/AK/Atomic.h @@ -112,6 +112,15 @@ static inline V* atomic_load(T volatile** var, MemoryOrder order = memory_order_ return __atomic_load_n(const_cast(var), order); } +static inline void atomic_pause() +{ +#if __has_builtin(__builtin_ia32_pause) + __builtin_ia32_pause(); +#elif __has_builtin(__builtin_arm_yield) + __builtin_arm_yield(); +#endif +} + template static inline void atomic_store(T volatile* var, T desired, MemoryOrder order = memory_order_seq_cst) noexcept { diff --git a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp index bf4eee7ad1b..26904aaa167 100644 --- a/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/AtomicsObject.cpp @@ -239,6 +239,7 @@ void AtomicsObject::initialize(Realm& realm) define_native_function(realm, vm.names.isLockFree, is_lock_free, 1, attr); define_native_function(realm, vm.names.load, load, 2, attr); define_native_function(realm, vm.names.or_, or_, 3, attr); + define_native_function(realm, vm.names.pause, pause, 0, attr); define_native_function(realm, vm.names.store, store, 3, attr); define_native_function(realm, vm.names.sub, sub, 3, attr); define_native_function(realm, vm.names.wait, wait, 4, attr); @@ -440,6 +441,40 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::or_) VERIFY_NOT_REACHED(); } +// 1 Atomics.pause ( [ N ] ), http://tc39.es/proposal-atomics-microwait/ +JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::pause) +{ + // NOTE: This value is arbitrary, but intends to put an upper bound on the spin loop of between ~10-100ns on most systems. + constexpr i32 MAXIMUM_ITERATIONS = 1000; + // NOTE: This value is arbitrary, but intends to account for function call overhead. + constexpr i32 DEFAULT_ITERATIONS = 100; + + // 1. If N is neither undefined nor an integral Number, throw a TypeError exception. + auto pause = vm.argument(0); + + if (!pause.is_undefined() && !pause.is_integral_number()) + return vm.throw_completion(ErrorType::NotAnIntegerOrUndefined, "pause time"); + + // 2. If the execution environment of the ECMAScript implementation supports signaling to the operating system or CPU that the current executing code is in a spin-wait loop, such as executing a pause CPU instruction, send that signal. + // When N is not undefined, it determines the number of times that signal is sent. + u32 N = DEFAULT_ITERATIONS; + if (!pause.is_undefined()) { + auto integral = pause.as_i32_clamped_integral_number(); + if (integral < 0) + N = MAXIMUM_ITERATIONS + max(integral, -MAXIMUM_ITERATIONS) + 1; + else + // Implementation note: `N` is not required to be the _number of times_ that the signal is sent, but it seems like reasonable behaviour regardless. + N = min(integral, MAXIMUM_ITERATIONS); + } + + // The number of times the signal is sent for an integral Number N is less than or equal to the number times it is sent for N + 1 if both N and N + 1 have the same sign. + for (; N != 0; N--) + AK::atomic_pause(); + + // 3. Return undefined. + return js_undefined(); +} + // 25.4.11 Atomics.store ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.store JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store) { diff --git a/Userland/Libraries/LibJS/Runtime/AtomicsObject.h b/Userland/Libraries/LibJS/Runtime/AtomicsObject.h index bf2404b6893..49fdd1040fa 100644 --- a/Userland/Libraries/LibJS/Runtime/AtomicsObject.h +++ b/Userland/Libraries/LibJS/Runtime/AtomicsObject.h @@ -28,6 +28,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(is_lock_free); JS_DECLARE_NATIVE_FUNCTION(load); JS_DECLARE_NATIVE_FUNCTION(or_); + JS_DECLARE_NATIVE_FUNCTION(pause); JS_DECLARE_NATIVE_FUNCTION(store); JS_DECLARE_NATIVE_FUNCTION(sub); JS_DECLARE_NATIVE_FUNCTION(wait); diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 96b9808364c..4f6679f4b4f 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -411,6 +411,7 @@ namespace JS { P(parse) \ P(parseFloat) \ P(parseInt) \ + P(pause) \ P(plainDate) \ P(plainDateISO) \ P(plainDateTime) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index d63e8adac60..aaf76f2303b 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -96,6 +96,7 @@ M(NotAConstructor, "{} is not a constructor") \ M(NotAFunction, "{} is not a function") \ M(NotAFunctionNoParam, "Not a function") \ + M(NotAnIntegerOrUndefined, "{} is neither an integer nor undefined") \ M(NotAnObject, "{} is not an object") \ M(NotAnObjectOfType, "Not an object of type {}") \ M(NotAnObjectOrNull, "{} is neither an object nor null") \ diff --git a/Userland/Libraries/LibJS/Runtime/Value.h b/Userland/Libraries/LibJS/Runtime/Value.h index 052587d4165..8653d5ea57d 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.h +++ b/Userland/Libraries/LibJS/Runtime/Value.h @@ -458,6 +458,19 @@ public: return static_cast(m_value.encoded & 0xFFFFFFFF); } + i32 as_i32_clamped_integral_number() const + { + VERIFY(is_int32() || is_finite_number()); + if (is_int32()) + return as_i32(); + double value = trunc(as_double()); + if (value > INT32_MAX) + return INT32_MAX; + if (value < INT32_MIN) + return INT32_MIN; + return static_cast(value); + } + bool to_boolean_slow_case() const; private: diff --git a/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.pause.js b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.pause.js new file mode 100644 index 00000000000..88b09b61905 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Atomics/Atomics.pause.js @@ -0,0 +1,24 @@ +test("invariants", () => { + expect(Atomics.pause).toHaveLength(0); +}); + +test("error cases", () => { + expect(() => { + Atomics.pause({}); + }).toThrow(TypeError); + + expect(() => { + Atomics.pause("not an integer"); + }).toThrow(TypeError); + + expect(() => { + Atomics.pause("0"); + }).toThrow(TypeError); +}); + +test("basic functionality", () => { + expect(Atomics.pause()).toBeUndefined(); + expect(Atomics.pause(0)).toBeUndefined(); + expect(Atomics.pause(1)).toBeUndefined(); + expect(Atomics.pause(-1)).toBeUndefined(); +});