diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d742839c5..c05b7ad38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,22 +37,30 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2025-10-31 + toolchain: nightly-2026-05-28 components: rustfmt, clippy, llvm-tools-preview, rustc-dev - name: Cache on ${{ github.ref_name }} uses: Swatinem/rust-cache@v2 with: shared-key: warm - - name: Install Dylint - uses: taiki-e/install-action@v2 + # Build Dylint from crates.io instead of using prebuilt binaries: the + # binaries attached to dylint's GitHub releases (since v6.0.1) are built + # inside the dylint repo and bake in a path dependency on + # /home/runner/work/dylint/dylint/driver, which breaks driver builds. + - name: Install cargo-dylint + uses: taiki-e/cache-cargo-install-action@v2 with: - tool: cargo-dylint,dylint-link + tool: cargo-dylint@6.0.1 + - name: Install dylint-link + uses: taiki-e/cache-cargo-install-action@v2 + with: + tool: dylint-link@6.0.1 - name: Check formatting run: cargo fmt --check - name: Clippy run: | cargo +stable clippy --all-targets -- -D warnings - cargo +nightly-2025-10-31 clippy --all-targets --all-features -- -D warnings + cargo +nightly-2026-05-28 clippy --all-targets --all-features -- -D warnings - name: Dylint tests working-directory: nova_lint run: cargo test diff --git a/Cargo.toml b/Cargo.toml index beeab8e49..7d304b0e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ console = "=0.16.3" ctrlc = "=3.5.2" ecmascript_atomics = { version = "=0.2.3" } fast-float = "=0.2.0" -hashbrown = "=0.17.0" +hashbrown = "=0.17.1" lexical = { version = "=7.0.5", default-features = false, features = [ "std", "write-integers", diff --git a/nova_cli/src/lib/child_hooks.rs b/nova_cli/src/lib/child_hooks.rs index 343d21934..6ebb14970 100644 --- a/nova_cli/src/lib/child_hooks.rs +++ b/nova_cli/src/lib/child_hooks.rs @@ -10,7 +10,7 @@ use std::{ collections::VecDeque, sync::{atomic::AtomicBool, mpsc}, thread, - time::Duration, + time::{Duration, Instant}, }; use nova_vm::ecmascript::{HostHooks, Job}; @@ -19,7 +19,7 @@ use crate::{ChildToHostMessage, HostToChildMessage}; pub struct CliChildHooks { promise_job_queue: RefCell>, - macrotask_queue: RefCell>, + macrotask_queue: RefCell, Job)>>, pub(crate) receiver: mpsc::Receiver, pub(crate) host_sender: mpsc::SyncSender, ready_to_leave: AtomicBool, @@ -78,9 +78,11 @@ impl CliChildHooks { let mut counter = 0u8; while !off_thread_job_queue.is_empty() { counter = counter.wrapping_add(1); - for (i, job) in off_thread_job_queue.iter().enumerate() { - if job.is_finished() { - let job = off_thread_job_queue.swap_remove(i); + let now = Instant::now(); + for (i, (deadline, job)) in off_thread_job_queue.iter().enumerate() { + let deadline_reached = deadline.is_none_or(|d| now >= d); + if deadline_reached && job.is_finished() { + let (_, job) = off_thread_job_queue.swap_remove(i); return Some(job); } } @@ -96,14 +98,19 @@ impl CliChildHooks { impl HostHooks for CliChildHooks { fn enqueue_generic_job(&self, job: Job) { - self.macrotask_queue.borrow_mut().push(job); + self.macrotask_queue.borrow_mut().push((None, job)); } fn enqueue_promise_job(&self, job: Job) { self.promise_job_queue.borrow_mut().push_back(job); } - fn enqueue_timeout_job(&self, _timeout_job: Job, _milliseconds: u64) {} + fn enqueue_timeout_job(&self, timeout_job: Job, milliseconds: u64) { + let deadline = Instant::now() + Duration::from_millis(milliseconds); + self.macrotask_queue + .borrow_mut() + .push((Some(deadline), timeout_job)); + } fn get_host_data(&self) -> &dyn std::any::Any { self diff --git a/nova_cli/src/lib/host_hooks.rs b/nova_cli/src/lib/host_hooks.rs index 71444afaa..09b3bd88f 100644 --- a/nova_cli/src/lib/host_hooks.rs +++ b/nova_cli/src/lib/host_hooks.rs @@ -5,8 +5,14 @@ //! The [`HostHooks`] implementation for the main thread. use std::{ - cell::RefCell, collections::VecDeque, fmt::Debug, path::PathBuf, rc::Rc, sync::mpsc, thread, - time::Duration, + cell::RefCell, + collections::VecDeque, + fmt::Debug, + path::PathBuf, + rc::Rc, + sync::mpsc, + thread, + time::{Duration, Instant}, }; use nova_vm::{ @@ -29,7 +35,7 @@ pub enum ChildToHostMessage { pub struct CliHostHooks { promise_job_queue: RefCell>, - macrotask_queue: RefCell>, + macrotask_queue: RefCell, Job)>>, pub(crate) receiver: mpsc::Receiver, pub(crate) own_sender: mpsc::SyncSender, pub(crate) child_senders: RefCell>>, @@ -83,9 +89,11 @@ impl CliHostHooks { let mut counter = 0u8; while !off_thread_job_queue.is_empty() { counter = counter.wrapping_add(1); - for (i, job) in off_thread_job_queue.iter().enumerate() { - if job.is_finished() { - let job = off_thread_job_queue.swap_remove(i); + let now = Instant::now(); + for (i, (deadline, job)) in off_thread_job_queue.iter().enumerate() { + let deadline_reached = deadline.is_none_or(|d| now >= d); + if deadline_reached && job.is_finished() { + let (_, job) = off_thread_job_queue.swap_remove(i); return Some(job); } } @@ -101,14 +109,19 @@ impl CliHostHooks { impl HostHooks for CliHostHooks { fn enqueue_generic_job(&self, job: Job) { - self.macrotask_queue.borrow_mut().push(job); + self.macrotask_queue.borrow_mut().push((None, job)); } fn enqueue_promise_job(&self, job: Job) { self.promise_job_queue.borrow_mut().push_back(job); } - fn enqueue_timeout_job(&self, _timeout_job: Job, _milliseconds: u64) {} + fn enqueue_timeout_job(&self, timeout_job: Job, milliseconds: u64) { + let deadline = Instant::now() + Duration::from_millis(milliseconds); + self.macrotask_queue + .borrow_mut() + .push((Some(deadline), timeout_job)); + } fn load_imported_module<'gc>( &self, diff --git a/nova_lint/Cargo.toml b/nova_lint/Cargo.toml index 74ddbc68a..25a865608 100644 --- a/nova_lint/Cargo.toml +++ b/nova_lint/Cargo.toml @@ -45,12 +45,14 @@ name = "spec_header_level" path = "ui/spec_header_level.rs" [dependencies] -clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "c936595d17413c1f08e162e117e504fb4ed126e4" } -dylint_linting = { version = "5.0.0", features = ["constituent"] } +# Must match the nightly toolchain pinned in .github/workflows/ci.yml: each +# clippy_utils release only builds with its contemporary nightly. +clippy_utils = "0.1.98" +dylint_linting = { version = "6.0.1", features = ["constituent"] } regex = "1" [dev-dependencies] -dylint_testing = "5.0.0" +dylint_testing = "6.0.1" nova_vm = { path = "../nova_vm" } [package.metadata.rust-analyzer] diff --git a/nova_lint/rust-toolchain b/nova_lint/rust-toolchain index 4e5ce19b2..39b925b85 100644 --- a/nova_lint/rust-toolchain +++ b/nova_lint/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2025-10-31" +channel = "nightly-2026-05-28" components = ["llvm-tools-preview", "rustc-dev"] diff --git a/nova_lint/src/utils.rs b/nova_lint/src/utils.rs index 3c2ab465f..1a79d2732 100644 --- a/nova_lint/src/utils.rs +++ b/nova_lint/src/utils.rs @@ -51,7 +51,7 @@ pub fn match_def_paths(cx: &LateContext<'_>, did: DefId, syms: &[&[&str]]) -> bo pub fn is_trait_item(cx: &LateContext<'_>, hir_id: HirId) -> bool { if let Node::Item(item) = cx.tcx.parent_hir_node(hir_id) { - matches!(item.kind, ItemKind::Trait(..)) + matches!(item.kind, ItemKind::Trait { .. }) } else { false } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs index 1a530432b..f885dd903 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/atomics_object.rs @@ -5,8 +5,7 @@ use std::{ hint::assert_unchecked, ops::ControlFlow, - sync::Arc, - thread::{self, JoinHandle}, + sync::{Arc, Mutex}, time::Duration, }; @@ -18,7 +17,7 @@ use crate::{ BigInt, Builtin, ExceptionType, InnerJob, Job, JsResult, Number, Numeric, OrdinaryObject, Promise, PromiseCapability, Realm, SharedArrayBuffer, SharedDataBlock, SharedTypedArray, String, TryError, TryResult, TypedArrayAbstractOperations, - TypedArrayWithBufferWitnessRecords, Value, WaitResult, WaiterRecord, + TypedArrayWithBufferWitnessRecords, Value, WaitResult, WaiterLists, WaiterRecord, builders::OrdinaryObjectBuilder, compare_exchange_in_buffer, for_any_typed_array, get_modify_set_value_in_buffer, get_value_from_buffer, make_typed_array_with_buffer_witness_record, number_convert_to_integer_or_infinity, @@ -1504,7 +1503,7 @@ fn do_wait_critical<'gc, const IS_ASYNC: bool, const IS_I64: bool>( let (new_guard, timeout) = waiter_record.wait_timeout(guard, dur); guard = new_guard; if timeout.timed_out() { - guard.remove_from_list(byte_index_in_buffer, waiter_record); + guard.remove_from_list(byte_index_in_buffer, &waiter_record); // 31. Perform LeaveCriticalSection(WL). // 32. If mode is sync, return waiterRecord.[[Result]]. @@ -1621,54 +1620,98 @@ fn create_wait_result_object<'gc>( .expect("Should perform GC here") } -#[derive(Debug)] +fn get_wait_async_job_waiters(data_block: &SharedDataBlock) -> &Mutex { + // SAFETY: the data block is a non-dangling clone captured in [`do_wait_critical`] after validation. + unsafe { data_block.get_or_init_waiters() } +} + struct WaitAsyncJobInner { + data_block: SharedDataBlock, + byte_index_in_buffer: usize, + waiter_record: Arc, promise_to_resolve: Global>, - join_handle: JoinHandle, _has_timeout: bool, } -#[derive(Debug)] #[repr(transparent)] pub(crate) struct WaitAsyncJob(Box); impl WaitAsyncJob { pub(crate) fn is_finished(&self) -> bool { - self.0.join_handle.is_finished() + self.0.waiter_record.is_notified() } pub(crate) fn _will_halt(&self) -> bool { self.0._has_timeout } - // NOTE: The reason for using `GcScope` here even though we could've gotten - // away with `NoGcScope` is that this is essentially a trait impl method, - // but currently without the trait. The job trait will be added eventually - // and we can get rid of this lint exception. - #[allow(unknown_lints, can_use_no_gc_scope)] - pub(crate) fn run<'gc>(self, agent: &mut Agent, gc: GcScope) -> JsResult<'gc, ()> { - let gc = gc.into_nogc(); - let promise = self.0.promise_to_resolve.take(agent).bind(gc); - let Ok(result) = self.0.join_handle.join() else { - // Foreign thread died; we can never resolve. - return Ok(()); + /// Implementation of the Job Abstract Closure for + /// [WaitAsyncTimeoutJob](https://tc39.es/ecma262/#sec-enqueueatomicswaitasynctimeoutjob), + /// for the cases where no timeout is specified. + pub(crate) fn run<'gc>(self, agent: &mut Agent, gc: NoGcScope<'gc, '_>) -> JsResult<'gc, ()> { + let waiters = get_wait_async_job_waiters(&self.0.data_block); + + let mut guard = waiters.lock().unwrap(); + let waiter_record = self.0.waiter_record; + guard.remove_from_list(self.0.byte_index_in_buffer, &waiter_record); + + let result = match waiter_record.get_result() { + Some(WaitResult::TimedOut) => WaitResult::TimedOut, + Some(WaitResult::Ok) => WaitResult::Ok, + None => { + waiter_record.set_result(WaitResult::Ok); + WaitResult::Ok + } }; + + let promise = self.0.promise_to_resolve.take(agent).bind(gc); + let promise_capability = PromiseCapability::from_promise(promise, true); + unwrap_try(promise_capability.try_resolve(agent, result.to_string().into(), gc)); + + drop(guard); + Ok(()) + } +} + +struct WaitAsyncTimeoutJobInner { + data_block: SharedDataBlock, + byte_index_in_buffer: usize, + waiter_record: Arc, +} + +pub(crate) struct WaitAsyncTimeoutJob(Box); + +impl WaitAsyncTimeoutJob { + pub(crate) fn run(self) { + let WaitAsyncTimeoutJobInner { + data_block, + byte_index_in_buffer, + waiter_record, + } = *self.0; + if waiter_record.get_result().is_some() { + return; + } + + let waiters = get_wait_async_job_waiters(&data_block); // a. Perform EnterCriticalSection(WL). + let mut guard = waiters.lock().unwrap(); + // b. If WL.[[Waiters]] contains waiterRecord, then // i. Let timeOfJobExecution be the time value (UTC) identifying the current time. // ii. Assert: ℝ(timeOfJobExecution) ≥ waiterRecord.[[TimeoutTime]] (ignoring potential non-monotonicity of time values). // iii. Set waiterRecord.[[Result]] to "timed-out". + waiter_record.set_result(WaitResult::TimedOut); + // iv. Perform RemoveWaiter(WL, waiterRecord). + guard.remove_from_list(byte_index_in_buffer, &waiter_record); + // v. Perform NotifyWaiter(WL, waiterRecord). + waiter_record.notify_waiters(); + // c. Perform LeaveCriticalSection(WL). - let promise_capability = PromiseCapability::from_promise(promise, true); - let result = match result { - WaitResult::Ok => BUILTIN_STRING_MEMORY.ok.into(), - WaitResult::TimedOut => BUILTIN_STRING_MEMORY.timed_out.into(), - }; - unwrap_try(promise_capability.try_resolve(agent, result, gc)); + drop(guard); + // d. Return unused. - Ok(()) } } @@ -1687,42 +1730,41 @@ fn enqueue_atomics_wait_async_job( gc: NoGcScope, ) { // 1. Let timeoutJob be a new Job Abstract Closure with no parameters that - // captures WL and waiterRecord and performs the following steps when - // called: - let handle = thread::spawn(move || { - // SAFETY: buffer is a cloned SharedDataBlock; non-dangling. - let waiters = unsafe { data_block.get_or_init_waiters() }; - let mut guard = waiters.lock().unwrap(); - - if t == u64::MAX { - waiter_record.wait(guard); - } else { - let dur = Duration::from_millis(t); - let (new_guard, timeout) = waiter_record.wait_timeout(guard, dur); - guard = new_guard; - if timeout.timed_out() { - guard.remove_from_list(byte_index_in_buffer, waiter_record); - - // 31. Perform LeaveCriticalSection(WL). - drop(guard); + // captures WL and waiterRecord and performs the following steps when called: + // 2. Let now be the time value (UTC) identifying the current time. + // 3. Let currentRealm be the current Realm Record. + // 4. Perform HostEnqueueTimeoutJob(timeoutJob, currentRealm, 𝔽(waiterRecord.[[TimeoutTime]]) - now). + let timeout_job_data = if t != u64::MAX { + Some(WaitAsyncTimeoutJobInner { + data_block: data_block.clone(), + byte_index_in_buffer, + waiter_record: waiter_record.clone(), + }) + } else { + None + }; - // 32. If mode is sync, return waiterRecord.[[Result]]. - return WaitResult::TimedOut; - } - } - WaitResult::Ok - }); let wait_async_job = Job { realm: Some(Global::new(agent, agent.current_realm(gc).unbind())), inner: InnerJob::WaitAsync(WaitAsyncJob(Box::new(WaitAsyncJobInner { + data_block, + byte_index_in_buffer, + waiter_record, promise_to_resolve: promise, - join_handle: handle, _has_timeout: t != u64::MAX, }))), }; - // 2. Let now be the time value (UTC) identifying the current time. - // 3. Let currentRealm be the current Realm Record. - // 4. Perform HostEnqueueTimeoutJob(timeoutJob, currentRealm, 𝔽(waiterRecord.[[TimeoutTime]]) - now). agent.host_hooks.enqueue_generic_job(wait_async_job); + + if let Some(inner) = timeout_job_data { + let wait_async_timeout_job = Job { + realm: Some(Global::new(agent, agent.current_realm(gc).unbind())), + inner: InnerJob::WaitAsyncTimeout(WaitAsyncTimeoutJob(Box::new(inner))), + }; + agent + .host_hooks + .enqueue_timeout_job(wait_async_timeout_job, t); + } + // 5. Return unused. } diff --git a/nova_vm/src/ecmascript/builtins/structured_data/json_object.rs b/nova_vm/src/ecmascript/builtins/structured_data/json_object.rs index 4819275b3..ed0067470 100644 --- a/nova_vm/src/ecmascript/builtins/structured_data/json_object.rs +++ b/nova_vm/src/ecmascript/builtins/structured_data/json_object.rs @@ -1008,7 +1008,7 @@ fn serialize_json_object<'a, 'b>( // i. Let separator be the string-concatenation of the code unit // 0x002C (COMMA), the code unit 0x000A (LINE FEED), and // state.[[Indent]]. - separator_string = format!(",\n{}", &state.indent).into_boxed_str(); + separator_string = format!(",\n{}", state.indent).into_boxed_str(); // ii. Let properties be the String value formed by concatenating // all the element Strings of partial with each adjacent pair // of Strings separated with separator. The separator String is @@ -1017,8 +1017,8 @@ fn serialize_json_object<'a, 'b>( // iii. Let final be the string-concatenation of "{", the code unit // 0x000A (LINE FEED), state.[[Indent]], properties, the code // unit 0x000A (LINE FEED), stepBack, and "}". - open_string = format!("{{\n{}", &state.indent).into_boxed_str(); - close_string = format!("\n{}}}", &step_back).into_boxed_str(); + open_string = format!("{{\n{}", state.indent).into_boxed_str(); + close_string = format!("\n{}}}", step_back).into_boxed_str(); ( open_string.as_ref(), separator_string.as_ref(), @@ -1170,7 +1170,7 @@ fn serialize_json_array<'a, 'b>( // b. Else, // i. Let separator be the string-concatenation of the code unit 0x002C // (COMMA), the code unit 0x000A (LINE FEED), and state.[[Indent]]. - separator_string = format!(",\n{}", &state.indent).into_boxed_str(); + separator_string = format!(",\n{}", state.indent).into_boxed_str(); // ii. Let properties be the String value formed by concatenating all // the element Strings of partial with each adjacent pair of // Strings separated with separator. The separator String is not @@ -1179,8 +1179,8 @@ fn serialize_json_array<'a, 'b>( // iii. Let final be the string-concatenation of "[", the code unit // 0x000A (LINE FEED), state.[[Indent]], properties, the code unit // 0x000A (LINE FEED), stepBack, and "]". - open_string = format!("[\n{}", &state.indent).into_boxed_str(); - close_string = format!("\n{}]", &step_back).into_boxed_str(); + open_string = format!("[\n{}", state.indent).into_boxed_str(); + close_string = format!("\n{}]", step_back).into_boxed_str(); ( open_string.as_ref(), separator_string.as_ref(), diff --git a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs index 56c87c442..2fe27b3fe 100644 --- a/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs +++ b/nova_vm/src/ecmascript/builtins/temporal/plain_time.rs @@ -143,7 +143,6 @@ pub(crate) fn create_temporal_plain_time<'gc>( /// a normal completion containing a Temporal.PlainTime or a throw completion. /// It adds/subtracts temporalDurationLike to/from temporalTime, returning a /// point in time that is in the future/past relative to temporalTime. -/// It performs the following steps when called: fn add_duration_to_time<'gc, const IS_ADD: bool>( agent: &mut Agent, plan_time: TemporalPlainTime, diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 1289c6be8..3a20cf3ed 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -25,10 +25,10 @@ use ahash::AHashMap; use crate::ecmascript::GlobalEnvironment; #[cfg(feature = "shared-array-buffer")] use crate::ecmascript::SharedArrayBuffer; -#[cfg(feature = "atomics")] -use crate::ecmascript::WaitAsyncJob; #[cfg(feature = "weak-refs")] use crate::ecmascript::{FinalizationRegistryCleanupJob, clear_kept_objects}; +#[cfg(feature = "atomics")] +use crate::ecmascript::{WaitAsyncJob, WaitAsyncTimeoutJob}; use crate::{ ecmascript::{ AbstractModuleMethods, Environment, ErrorHeapData, ExecutionContext, Function, @@ -258,6 +258,8 @@ pub(crate) enum InnerJob { PromiseReaction(PromiseReactionJob), #[cfg(feature = "atomics")] WaitAsync(WaitAsyncJob), + #[cfg(feature = "atomics")] + WaitAsyncTimeout(WaitAsyncTimeoutJob), #[cfg(feature = "weak-refs")] FinalizationRegistry(FinalizationRegistryCleanupJob), } @@ -314,7 +316,12 @@ impl Job { InnerJob::PromiseResolveThenable(job) => job.run(agent, gc), InnerJob::PromiseReaction(job) => job.run(agent, gc), #[cfg(feature = "atomics")] - InnerJob::WaitAsync(job) => job.run(agent, gc), + InnerJob::WaitAsync(job) => job.run(agent, gc.into_nogc()), + #[cfg(feature = "atomics")] + InnerJob::WaitAsyncTimeout(job) => { + job.run(); + Ok(()) + } #[cfg(feature = "weak-refs")] InnerJob::FinalizationRegistry(job) => { job.run(agent, gc); diff --git a/nova_vm/src/ecmascript/execution/environments/private_environment.rs b/nova_vm/src/ecmascript/execution/environments/private_environment.rs index 82aeca3b4..65ded6a24 100644 --- a/nova_vm/src/ecmascript/execution/environments/private_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/private_environment.rs @@ -485,7 +485,7 @@ impl HeapMarkAndSweep for PrivateEnvironmentRecord { let mut replacements = Vec::new(); // Sweep all binding values, while also sweeping keys and making note // of all changes in them: Those need to be updated in a separate loop. - for (key, _) in names.iter_mut() { + for key in names.keys() { if let String::String(old_key) = key { let old_key = *old_key; let mut new_key = old_key; diff --git a/nova_vm/src/ecmascript/types/spec/data_block.rs b/nova_vm/src/ecmascript/types/spec/data_block.rs index 5efb6cfdd..a5f632b45 100644 --- a/nova_vm/src/ecmascript/types/spec/data_block.rs +++ b/nova_vm/src/ecmascript/types/spec/data_block.rs @@ -10,7 +10,7 @@ use std::{ hint::assert_unchecked, sync::{ Arc, Condvar, Mutex, MutexGuard, WaitTimeoutResult, - atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicPtr, AtomicU8, AtomicUsize, Ordering}, }, time::Duration, }; @@ -426,10 +426,22 @@ impl SharedDataBlockMaxByteLength { } #[cfg(feature = "shared-array-buffer")] -#[derive(Default)] +#[derive(Debug)] pub(crate) struct WaiterRecord { condvar: Condvar, notified: AtomicBool, + result: AtomicU8, +} + +#[cfg(feature = "shared-array-buffer")] +impl Default for WaiterRecord { + fn default() -> Self { + Self { + condvar: Condvar::default(), + notified: AtomicBool::default(), + result: AtomicU8::new(u8::MAX), + } + } } #[cfg(feature = "shared-array-buffer")] @@ -448,6 +460,7 @@ impl WaiterRecord { let lock_result = self .condvar .wait_while(guard, |_| !self.notified.load(Ordering::Relaxed)); + match lock_result { Ok(_) => (), Err(e) => panic!( @@ -472,14 +485,40 @@ impl WaiterRecord { ), } } + + pub(crate) fn is_notified(&self) -> bool { + self.notified.load(Ordering::Relaxed) + } + + pub(crate) fn set_result(&self, result: WaitResult) { + self.result.store(result as u8, Ordering::Relaxed); + } + + pub(crate) fn get_result(&self) -> Option { + match self.result.load(Ordering::Relaxed) { + 0 => Some(WaitResult::Ok), + 1 => Some(WaitResult::TimedOut), + _ => None, + } + } } -/// Result of an `Atomics.wait` or `Atomics.waitAsync` operation. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg(feature = "shared-array-buffer")] +#[repr(u8)] pub(crate) enum WaitResult { - Ok, - TimedOut, + Ok = 0, + TimedOut = 1, +} + +#[cfg(feature = "shared-array-buffer")] +impl WaitResult { + pub(crate) fn to_string(self) -> crate::ecmascript::String<'static> { + match self { + WaitResult::Ok => crate::ecmascript::BUILTIN_STRING_MEMORY.ok, + WaitResult::TimedOut => crate::ecmascript::BUILTIN_STRING_MEMORY.timed_out, + } + } } #[cfg(feature = "shared-array-buffer")] @@ -502,12 +541,12 @@ impl WaiterList { self.waiters.push_back(w); } - pub(crate) fn remove(&mut self, w: Arc) -> bool { + pub(crate) fn remove(&mut self, w: &Arc) -> bool { let Some(index) = self .waiters .iter() .enumerate() - .find(|(_, e)| Arc::ptr_eq(e, &w)) + .find(|(_, e)| Arc::ptr_eq(e, w)) .map(|(i, _)| i) else { return false; @@ -533,7 +572,7 @@ impl WaiterLists { self.map.entry(index).or_default().push(w); } - pub(crate) fn remove_from_list(&mut self, index: usize, w: Arc) { + pub(crate) fn remove_from_list(&mut self, index: usize, w: &Arc) { match self.map.entry(index) { Entry::Occupied(mut entry) => { if entry.get_mut().remove(w) && entry.get().is_empty() { @@ -1180,13 +1219,7 @@ pub(crate) fn create_byte_data_block<'a>( // 1. If size > 2**53 - 1, throw a RangeError exception. if let Some(db) = usize::try_from(size) .ok() - .and_then(|size| { - if size as u64 > DATA_BLOCK_SIZE_LIMIT { - None - } else { - Some(size) - } - }) + .filter(|&size| size as u64 <= DATA_BLOCK_SIZE_LIMIT) .and_then(DataBlock::new) { // 2. Let db be a new Data Block value consisting of size bytes. @@ -1230,13 +1263,7 @@ pub(crate) unsafe fn create_shared_byte_data_block<'a>( // RangeError exception. if let Some(db) = usize::try_from(size) .ok() - .and_then(|size| { - if size as u64 > DATA_BLOCK_SIZE_LIMIT { - None - } else { - Some(size) - } - }) + .filter(|&size| size as u64 <= DATA_BLOCK_SIZE_LIMIT) .and_then(|_| { // SAFETY: function precondition unsafe { @@ -1338,8 +1365,12 @@ pub(crate) fn copy_shared_data_block_bytes( ) { // 1. Assert: fromBlock and toBlock are distinct values. debug_assert!(unsafe { - to_block.ptr.as_ptr().add(to_block.max_byte_length()) <= from_block.ptr.as_ptr() - || from_block.ptr.as_ptr().add(from_block.max_byte_length()) <= to_block.ptr.as_ptr() + to_block.ptr.as_ptr().byte_add(to_block.max_byte_length()) <= from_block.ptr.as_ptr() + || from_block + .ptr + .as_ptr() + .byte_add(from_block.max_byte_length()) + <= to_block.ptr.as_ptr() }); // 2. Let fromSize be the number of bytes in fromBlock. let from_size = from_block.max_byte_length(); diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 0a9593d09..97fb6d998 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -2294,7 +2294,7 @@ impl