From d3c91905da5d3987f3a41489088530ee3c8b1365 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 9 Feb 2026 10:32:41 +0900 Subject: [PATCH 1/3] `std` feature for common - Gate OS-dependent modules behind `#[cfg(feature = "std")]` - Replace `std::f64` with `core::f64` in float_ops - Replace `std::process::abort` with panic in refcount - Remove `thread_local` from levenshtein (stack buffer) - Split static_cell into threading/non_threading/no_std --- crates/common/Cargo.toml | 2 + crates/common/src/float_ops.rs | 2 +- crates/common/src/lib.rs | 9 ++-- crates/common/src/refcount.rs | 13 ++++- crates/common/src/static_cell.rs | 83 ++++++++++++++++++++++++++------ crates/common/src/str.rs | 58 +++++++++------------- 6 files changed, 113 insertions(+), 54 deletions(-) diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 9fd7ea3880a..679c7cd4a7b 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -9,6 +9,8 @@ repository.workspace = true license.workspace = true [features] +default = ["std"] +std = [] threading = ["parking_lot"] wasm_js = ["getrandom/wasm_js"] diff --git a/crates/common/src/float_ops.rs b/crates/common/src/float_ops.rs index b431e793139..c6b7c71e494 100644 --- a/crates/common/src/float_ops.rs +++ b/crates/common/src/float_ops.rs @@ -1,6 +1,6 @@ +use core::f64; use malachite_bigint::{BigInt, ToBigInt}; use num_traits::{Float, Signed, ToPrimitive, Zero}; -use std::f64; pub const fn decompose_float(value: f64) -> (f64, i32) { if 0.0 == value { diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index d52ed063867..e514c17541f 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,5 +1,7 @@ //! A crate to hold types and functions common to all rustpython components. +#![cfg_attr(not(feature = "std"), no_std)] + extern crate alloc; #[macro_use] @@ -10,10 +12,10 @@ pub mod atomic; pub mod borrow; pub mod boxvec; pub mod cformat; -#[cfg(any(unix, windows, target_os = "wasi"))] +#[cfg(all(feature = "std", any(unix, windows, target_os = "wasi")))] pub mod crt_fd; pub mod encodings; -#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] +#[cfg(all(feature = "std", any(not(target_arch = "wasm32"), target_os = "wasi")))] pub mod fileutils; pub mod float_ops; pub mod format; @@ -21,13 +23,14 @@ pub mod hash; pub mod int; pub mod linked_list; pub mod lock; +#[cfg(feature = "std")] pub mod os; pub mod rand; pub mod rc; pub mod refcount; pub mod static_cell; pub mod str; -#[cfg(windows)] +#[cfg(all(feature = "std", windows))] pub mod windows; pub use rustpython_wtf8 as wtf8; diff --git a/crates/common/src/refcount.rs b/crates/common/src/refcount.rs index c7038667099..4a8ed0bf51d 100644 --- a/crates/common/src/refcount.rs +++ b/crates/common/src/refcount.rs @@ -1,5 +1,14 @@ use crate::atomic::{Ordering::*, PyAtomic, Radium}; +#[inline(never)] +#[cold] +fn refcount_overflow() -> ! { + #[cfg(feature = "std")] + std::process::abort(); + #[cfg(not(feature = "std"))] + core::panic!("refcount overflow"); +} + /// from alloc::sync /// A soft limit on the amount of references that may be made to an `Arc`. /// @@ -36,7 +45,7 @@ impl RefCount { let old_size = self.strong.fetch_add(1, Relaxed); if old_size & Self::MASK == Self::MASK { - std::process::abort(); + refcount_overflow(); } } @@ -46,7 +55,7 @@ impl RefCount { let old_size = self.strong.fetch_add(n, Relaxed); if old_size & Self::MASK > Self::MASK - n { - std::process::abort(); + refcount_overflow(); } } diff --git a/crates/common/src/static_cell.rs b/crates/common/src/static_cell.rs index a8beee08206..61f97a97305 100644 --- a/crates/common/src/static_cell.rs +++ b/crates/common/src/static_cell.rs @@ -1,4 +1,53 @@ -#[cfg(not(feature = "threading"))] +#[cfg(feature = "threading")] +mod threading { + use crate::lock::OnceCell; + + pub struct StaticCell { + inner: OnceCell, + } + + impl StaticCell { + #[doc(hidden)] + pub const fn _from_once_cell(inner: OnceCell) -> Self { + Self { inner } + } + + pub fn get(&'static self) -> Option<&'static T> { + self.inner.get() + } + + pub fn set(&'static self, value: T) -> Result<(), T> { + self.inner.set(value) + } + + pub fn get_or_init(&'static self, f: F) -> &'static T + where + F: FnOnce() -> T, + { + self.inner.get_or_init(f) + } + + pub fn get_or_try_init(&'static self, f: F) -> Result<&'static T, E> + where + F: FnOnce() -> Result, + { + self.inner.get_or_try_init(f) + } + } + + #[macro_export] + macro_rules! static_cell { + ($($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty;)+) => { + $($(#[$attr])* + $vis static $name: $crate::static_cell::StaticCell<$t> = + $crate::static_cell::StaticCell::_from_once_cell($crate::lock::OnceCell::new());)+ + }; + } +} +#[cfg(feature = "threading")] +pub use threading::*; + +#[cfg(all(not(feature = "threading"), feature = "std"))] mod non_threading { use crate::lock::OnceCell; use std::thread::LocalKey; @@ -22,12 +71,10 @@ mod non_threading { } pub fn set(&'static self, value: T) -> Result<(), T> { - // thread-safe because it's a unsync::OnceCell self.inner.with(|x| { if x.get().is_some() { Err(value) } else { - // will never fail let _ = x.set(leak(value)); Ok(()) } @@ -65,43 +112,51 @@ mod non_threading { }; } } -#[cfg(not(feature = "threading"))] +#[cfg(all(not(feature = "threading"), feature = "std"))] pub use non_threading::*; -#[cfg(feature = "threading")] -mod threading { +// Same as `threading` variant, but wraps unsync::OnceCell with Sync. +#[cfg(all(not(feature = "threading"), not(feature = "std")))] +mod no_std { use crate::lock::OnceCell; + // unsync::OnceCell is !Sync, but without std there can be no threads. + struct SyncOnceCell(OnceCell); + // SAFETY: Without std, threading is impossible. + unsafe impl Sync for SyncOnceCell {} + pub struct StaticCell { - inner: OnceCell, + inner: SyncOnceCell, } impl StaticCell { #[doc(hidden)] pub const fn _from_once_cell(inner: OnceCell) -> Self { - Self { inner } + Self { + inner: SyncOnceCell(inner), + } } pub fn get(&'static self) -> Option<&'static T> { - self.inner.get() + self.inner.0.get() } pub fn set(&'static self, value: T) -> Result<(), T> { - self.inner.set(value) + self.inner.0.set(value) } pub fn get_or_init(&'static self, f: F) -> &'static T where F: FnOnce() -> T, { - self.inner.get_or_init(f) + self.inner.0.get_or_init(f) } pub fn get_or_try_init(&'static self, f: F) -> Result<&'static T, E> where F: FnOnce() -> Result, { - self.inner.get_or_try_init(f) + self.inner.0.get_or_try_init(f) } } @@ -114,5 +169,5 @@ mod threading { }; } } -#[cfg(feature = "threading")] -pub use threading::*; +#[cfg(all(not(feature = "threading"), not(feature = "std")))] +pub use no_std::*; diff --git a/crates/common/src/str.rs b/crates/common/src/str.rs index 35588c5dc6d..79c407909ff 100644 --- a/crates/common/src/str.rs +++ b/crates/common/src/str.rs @@ -466,9 +466,6 @@ impl fmt::Display for UnicodeEscapeCodepoint { } pub mod levenshtein { - use core::cell::RefCell; - use std::thread_local; - pub const MOVE_COST: usize = 2; const CASE_COST: usize = 1; const MAX_STRING_SIZE: usize = 40; @@ -490,13 +487,6 @@ pub mod levenshtein { } pub fn levenshtein_distance(a: &[u8], b: &[u8], max_cost: usize) -> usize { - thread_local! { - #[allow(clippy::declare_interior_mutable_const, reason = "thread-local scratch buffer uses const initializer with RefCell")] - static BUFFER: RefCell<[usize; MAX_STRING_SIZE]> = const { - RefCell::new([0usize; MAX_STRING_SIZE]) - }; - } - if a == b { return 0; } @@ -535,33 +525,33 @@ pub mod levenshtein { return max_cost + 1; } - BUFFER.with_borrow_mut(|buffer| { - for (i, x) in buffer.iter_mut().take(a_end).enumerate() { - *x = (i + 1) * MOVE_COST; - } + let mut buffer = [0usize; MAX_STRING_SIZE]; - let mut result = 0usize; - for (b_index, b_code) in b_bytes[b_begin..(b_begin + b_end)].iter().enumerate() { - result = b_index * MOVE_COST; - let mut distance = result; - let mut minimum = usize::MAX; - for (a_index, a_code) in a_bytes[a_begin..(a_begin + a_end)].iter().enumerate() { - let substitute = distance + substitution_cost(*b_code, *a_code); - distance = buffer[a_index]; - let insert_delete = usize::min(result, distance) + MOVE_COST; - result = usize::min(insert_delete, substitute); - - buffer[a_index] = result; - if result < minimum { - minimum = result; - } - } - if minimum > max_cost { - return max_cost + 1; + for (i, x) in buffer.iter_mut().take(a_end).enumerate() { + *x = (i + 1) * MOVE_COST; + } + + let mut result = 0usize; + for (b_index, b_code) in b_bytes[b_begin..(b_begin + b_end)].iter().enumerate() { + result = b_index * MOVE_COST; + let mut distance = result; + let mut minimum = usize::MAX; + for (a_index, a_code) in a_bytes[a_begin..(a_begin + a_end)].iter().enumerate() { + let substitute = distance + substitution_cost(*b_code, *a_code); + distance = buffer[a_index]; + let insert_delete = usize::min(result, distance) + MOVE_COST; + result = usize::min(insert_delete, substitute); + + buffer[a_index] = result; + if result < minimum { + minimum = result; } } - result - }) + if minimum > max_cost { + return max_cost + 1; + } + } + result } } From c2cb32c7770825aa2d4ef3314d6e90593308f398 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 9 Feb 2026 12:53:14 +0900 Subject: [PATCH 2/3] `std` for codegen --- crates/codegen/Cargo.toml | 4 ++++ crates/codegen/src/lib.rs | 1 + crates/codegen/src/symboltable.rs | 22 +++++++++++++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index ce7e8d74f59..78065962fff 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -8,6 +8,10 @@ rust-version.workspace = true repository.workspace = true license.workspace = true +[features] +default = ["std"] +std = ["thiserror/std", "itertools/use_std"] + [dependencies] rustpython-compiler-core = { workspace = true } rustpython-literal = {workspace = true } diff --git a/crates/codegen/src/lib.rs b/crates/codegen/src/lib.rs index 9dd7384170a..c1a318c19cd 100644 --- a/crates/codegen/src/lib.rs +++ b/crates/codegen/src/lib.rs @@ -1,4 +1,5 @@ //! Compile a Python AST or source code into bytecode consumable by RustPython. +#![cfg_attr(not(feature = "std"), no_std)] #![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/main/logo.png")] #![doc(html_root_url = "https://docs.rs/rustpython-compiler/")] diff --git a/crates/codegen/src/symboltable.rs b/crates/codegen/src/symboltable.rs index bbda2914f04..b175e40d487 100644 --- a/crates/codegen/src/symboltable.rs +++ b/crates/codegen/src/symboltable.rs @@ -296,8 +296,8 @@ fn drop_class_free(symbol_table: &mut SymbolTable, newfree: &mut IndexSet; mod stack { + use alloc::vec::Vec; use core::ptr::NonNull; - use std::panic; pub struct StackStack { v: Vec>, } @@ -309,14 +309,30 @@ mod stack { impl StackStack { /// Appends a reference to this stack for the duration of the function `f`. When `f` /// returns, the reference will be popped off the stack. + #[cfg(feature = "std")] pub fn with_append(&mut self, x: &mut T, f: F) -> R where F: FnOnce(&mut Self) -> R, { self.v.push(x.into()); - let res = panic::catch_unwind(panic::AssertUnwindSafe(|| f(self))); + let res = std::panic::catch_unwind(core::panic::AssertUnwindSafe(|| f(self))); self.v.pop(); - res.unwrap_or_else(|x| panic::resume_unwind(x)) + res.unwrap_or_else(|x| std::panic::resume_unwind(x)) + } + + /// Appends a reference to this stack for the duration of the function `f`. When `f` + /// returns, the reference will be popped off the stack. + /// + /// Without std, panic cleanup is not guaranteed (no catch_unwind). + #[cfg(not(feature = "std"))] + pub fn with_append(&mut self, x: &mut T, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.v.push(x.into()); + let result = f(self); + self.v.pop(); + result } pub fn iter(&self) -> impl DoubleEndedIterator + '_ { From f820a26d2a077ca93beb5a5d9097560c65e64d03 Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 9 Feb 2026 12:55:06 +0900 Subject: [PATCH 3/3] `no_std` for pylib --- crates/pylib/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/pylib/src/lib.rs b/crates/pylib/src/lib.rs index afdff75b725..db957f633fc 100644 --- a/crates/pylib/src/lib.rs +++ b/crates/pylib/src/lib.rs @@ -2,6 +2,8 @@ //! common way to use this crate is to just add the `"freeze-stdlib"` feature to `rustpython-vm`, //! in order to automatically include the python part of the standard library into the binary. +#![no_std] + // windows needs to read the symlink out of `Lib` as git turns it into a text file, // so build.rs sets this env var pub const LIB_PATH: &str = match option_env!("win_lib_path") {