LibJS: Implement tc39/proposal-atomics-microwait (Atomics.pause)
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-14, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run

Implements the https://github.com/tc39/proposal-atomics-microwait
proposal which has recently hit Stage 3.

This commit passes all relevant tests in `test262`.
This commit is contained in:
Jonne Ransijn 2024-11-02 00:09:07 +01:00 committed by Tim Flynn
parent 01c2ecf355
commit 755b83c01a
Notes: github-actions[bot] 2024-11-03 13:23:18 +00:00
7 changed files with 84 additions and 0 deletions

View file

@ -112,6 +112,15 @@ static inline V* atomic_load(T volatile** var, MemoryOrder order = memory_order_
return __atomic_load_n(const_cast<V**>(var), order); return __atomic_load_n(const_cast<V**>(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<typename T> template<typename T>
static inline void atomic_store(T volatile* var, T desired, MemoryOrder order = memory_order_seq_cst) noexcept static inline void atomic_store(T volatile* var, T desired, MemoryOrder order = memory_order_seq_cst) noexcept
{ {

View file

@ -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.isLockFree, is_lock_free, 1, attr);
define_native_function(realm, vm.names.load, load, 2, 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.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.store, store, 3, attr);
define_native_function(realm, vm.names.sub, sub, 3, attr); define_native_function(realm, vm.names.sub, sub, 3, attr);
define_native_function(realm, vm.names.wait, wait, 4, attr); define_native_function(realm, vm.names.wait, wait, 4, attr);
@ -440,6 +441,40 @@ JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::or_)
VERIFY_NOT_REACHED(); 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<TypeError>(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 // 25.4.11 Atomics.store ( typedArray, index, value ), https://tc39.es/ecma262/#sec-atomics.store
JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store) JS_DEFINE_NATIVE_FUNCTION(AtomicsObject::store)
{ {

View file

@ -28,6 +28,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(is_lock_free); JS_DECLARE_NATIVE_FUNCTION(is_lock_free);
JS_DECLARE_NATIVE_FUNCTION(load); JS_DECLARE_NATIVE_FUNCTION(load);
JS_DECLARE_NATIVE_FUNCTION(or_); JS_DECLARE_NATIVE_FUNCTION(or_);
JS_DECLARE_NATIVE_FUNCTION(pause);
JS_DECLARE_NATIVE_FUNCTION(store); JS_DECLARE_NATIVE_FUNCTION(store);
JS_DECLARE_NATIVE_FUNCTION(sub); JS_DECLARE_NATIVE_FUNCTION(sub);
JS_DECLARE_NATIVE_FUNCTION(wait); JS_DECLARE_NATIVE_FUNCTION(wait);

View file

@ -411,6 +411,7 @@ namespace JS {
P(parse) \ P(parse) \
P(parseFloat) \ P(parseFloat) \
P(parseInt) \ P(parseInt) \
P(pause) \
P(plainDate) \ P(plainDate) \
P(plainDateISO) \ P(plainDateISO) \
P(plainDateTime) \ P(plainDateTime) \

View file

@ -96,6 +96,7 @@
M(NotAConstructor, "{} is not a constructor") \ M(NotAConstructor, "{} is not a constructor") \
M(NotAFunction, "{} is not a function") \ M(NotAFunction, "{} is not a function") \
M(NotAFunctionNoParam, "Not a function") \ M(NotAFunctionNoParam, "Not a function") \
M(NotAnIntegerOrUndefined, "{} is neither an integer nor undefined") \
M(NotAnObject, "{} is not an object") \ M(NotAnObject, "{} is not an object") \
M(NotAnObjectOfType, "Not an object of type {}") \ M(NotAnObjectOfType, "Not an object of type {}") \
M(NotAnObjectOrNull, "{} is neither an object nor null") \ M(NotAnObjectOrNull, "{} is neither an object nor null") \

View file

@ -458,6 +458,19 @@ public:
return static_cast<i32>(m_value.encoded & 0xFFFFFFFF); return static_cast<i32>(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<i32>(value);
}
bool to_boolean_slow_case() const; bool to_boolean_slow_case() const;
private: private:

View file

@ -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();
});