Skip to content

Commit

Permalink
Merge pull request #747 from schungx/master
Browse files Browse the repository at this point in the history
wasm32-wasi
  • Loading branch information
schungx committed Jul 26, 2023
2 parents 84fd228 + 12a536c commit 26fd283
Show file tree
Hide file tree
Showing 27 changed files with 533 additions and 608 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Bug fixes
* Fixes a panic when using `this` as the first parameter in a namespace-qualified function call.
* Comparing two different data types (e.g. a custom type and a standard type) now correctly defaults to `false` (except for `!=` which defaults to `true`).
* `max` and `min` for integers, strings and characters were missing from the standard library. They are now added.
* O/S features such as file access and time are no longer disabled when using `wasm32-wasi` (or any WASM target other than `wasm32-unknown`).

Enhancements
------------

* [`once_cell`](https://crates.io/crates/once_cell) is used in `std` environments instead of the home-brew `SusLock` (which is still kept for `no-std`).

New features
------------
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"]
smallvec = { version = "1.7", default-features = false, features = ["union", "const_new", "const_generics"] }
ahash = { version = "0.8.2", default-features = false, features = ["compile-time-rng"] }
num-traits = { version = "0.2", default-features = false }
once_cell = { version = "1", default-features = false, features = ["race"] }
bitflags = { version = "1", default-features = false }
smartstring = { version = "1", default-features = false }
rhai_codegen = { version = "1.5.0", path = "codegen", default-features = false }
Expand All @@ -46,7 +47,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
## Default features: `std`, uses runtime random numbers for hashing.
default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng
## Standard features: uses compile-time random number for hashing.
std = ["ahash/std", "num-traits/std", "smartstring/std"]
std = ["once_cell/std", "ahash/std", "num-traits/std", "smartstring/std"]

#! ### Enable Special Functionalities

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Standard features
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) (including packed [byte arrays](https://rhai.rs/book/language/blobs.html)) and [object maps](https://rhai.rs/book/language/object-maps.html).
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
* Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring).
* Few dependencies - currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`once_cell`](https://crates.io/crates/once_cell), [`ahash`](https://crates.io/crates/ahash), [`bitflags`](https://crates.io/crates/bitflags) and [`smartstring`](https://crates.io/crates/smartstring).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
* Scripts are [optimized](https://rhai.rs/book/engine/optimize) (useful for template-based machine-generated scripts).
Expand Down
6 changes: 4 additions & 2 deletions src/api/definitions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ impl Definitions<'_> {
/// specified directory.
///
/// This function creates the directories and overrides any existing files if needed.
#[cfg(all(not(feature = "no_std"), not(target_family = "wasm")))]
#[cfg(not(feature = "no_std"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
#[inline]
pub fn write_to_dir(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
use std::fs;
Expand All @@ -161,7 +162,8 @@ impl Definitions<'_> {
/// Output all definitions merged into a single file.
///
/// The parent directory must exist but the file will be created or overwritten as needed.
#[cfg(all(not(feature = "no_std"), not(target_family = "wasm")))]
#[cfg(not(feature = "no_std"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
#[inline(always)]
pub fn write_to_file(&self, path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
std::fs::write(path, self.single_file())
Expand Down
6 changes: 3 additions & 3 deletions src/api/deprecated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::prelude::v1::*;
use crate::func::register::Mut;

#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
use std::path::PathBuf;

impl Engine {
Expand All @@ -29,7 +29,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
#[inline(always)]
pub fn consume_file(&self, path: PathBuf) -> RhaiResultOf<()> {
self.run_file(path)
Expand All @@ -47,7 +47,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
#[inline(always)]
pub fn consume_file_with_scope(&self, scope: &mut Scope, path: PathBuf) -> RhaiResultOf<()> {
self.run_file_with_scope(scope, path)
Expand Down
2 changes: 1 addition & 1 deletion src/api/files.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Module that defines the public file-based API of [`Engine`].
#![cfg(not(feature = "no_std"))]
#![cfg(not(target_family = "wasm"))]
#![cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]

use crate::types::dynamic::Variant;
use crate::{Engine, RhaiResultOf, Scope, AST, ERR};
Expand Down
194 changes: 15 additions & 179 deletions src/config/hashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,184 +17,16 @@
//! ```sh
//! env RHAI_AHASH_SEED ="[236,800,954,213]"
//! ```
// [236,800,954,213], haha funny yume nikki reference epic uboachan face numberworld nexus moment 100

use crate::config::hashing_env;
use core::panic::{RefUnwindSafe, UnwindSafe};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
cell::UnsafeCell,
marker::PhantomData,
mem,
mem::MaybeUninit,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
use super::hashing_env;

// omg its hokma from record team here to record our locks
// what does this do?
// so what this does is keep track of a global address in memory that acts as a global lock
// i stole this from crossbeam so read their docs for more
#[must_use]
struct HokmaLock {
lock: AtomicUsize,
}

impl HokmaLock {
#[inline(always)]
pub const fn new() -> Self {
Self {
lock: AtomicUsize::new(0),
}
}

pub fn write(&'static self) -> WhenTheHokmaSuppression {
loop {
// We are only interested in error results
if let Err(previous) =
self.lock
.compare_exchange(1, 1, Ordering::SeqCst, Ordering::SeqCst)
{
// If we failed, previous cannot be 1
return WhenTheHokmaSuppression {
hokma: self,
state: previous,
};
}
}
}
}

struct WhenTheHokmaSuppression {
hokma: &'static HokmaLock,
state: usize,
}

impl WhenTheHokmaSuppression {
#[inline]
pub fn the_price_of_silence(self) {
self.hokma.lock.store(self.state, Ordering::SeqCst);
mem::forget(self);
}
}

impl Drop for WhenTheHokmaSuppression {
#[inline]
fn drop(&mut self) {
self.hokma
.lock
.store(self.state.wrapping_add(2), Ordering::SeqCst);
}
}
#[cfg(feature = "std")]
pub use once_cell::sync::OnceCell;

#[inline(always)]
fn hokmalock(address: usize) -> &'static HokmaLock {
const LEN: usize = 787;
#[allow(clippy::declare_interior_mutable_const)]
const LCK: HokmaLock = HokmaLock::new();
static RECORDS: [HokmaLock; LEN] = [LCK; LEN];
#[cfg(not(feature = "std"))]
pub use once_cell::race::OnceBox as OnceCell;

&RECORDS[address % LEN]
}

/// # Safety
///
/// LOL, there is a reason its called `SusLock`
#[must_use]
pub struct SusLock<T: 'static> {
initialized: AtomicBool,
data: UnsafeCell<MaybeUninit<T>>,
_marker: PhantomData<T>,
}

impl<T: 'static> Default for SusLock<T> {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}

impl<T: 'static> SusLock<T> {
/// Create a new [`SusLock`].
#[inline]
pub const fn new() -> Self {
Self {
initialized: AtomicBool::new(false),
data: UnsafeCell::new(MaybeUninit::uninit()),
_marker: PhantomData,
}
}

/// Is the [`SusLock`] initialized?
#[inline(always)]
#[must_use]
pub fn is_initialized(&self) -> bool {
self.initialized.load(Ordering::SeqCst)
}

/// Return the value of the [`SusLock`] (if initialized).
#[inline]
#[must_use]
pub fn get(&self) -> Option<&'static T> {
if self.initialized.load(Ordering::SeqCst) {
let hokma = hokmalock(self.data.get() as usize);
// we forgo the optimistic read, because we don't really care
let guard = hokma.write();
let cast: *const T = self.data.get().cast();
let val = unsafe { &*cast.cast::<T>() };
guard.the_price_of_silence();
Some(val)
} else {
None
}
}

/// Return the value of the [`SusLock`], initializing it if not yet done.
#[inline]
#[must_use]
pub fn get_or_init(&self, f: impl FnOnce() -> T) -> &'static T {
if !self.initialized.load(Ordering::SeqCst) {
self.initialized.store(true, Ordering::SeqCst);
let hokma = hokmalock(self.data.get() as usize);
hokma.write();
unsafe {
self.data.get().write(MaybeUninit::new(f()));
}
}

self.get().unwrap()
}

/// Initialize the value of the [`SusLock`].
///
/// # Error
///
/// If the [`SusLock`] has already been initialized, the current value is returned as error.
#[inline]
pub fn init(&self, value: T) -> Result<(), T> {
if self.initialized.load(Ordering::SeqCst) {
Err(value)
} else {
let _ = self.get_or_init(|| value);
Ok(())
}
}
}

unsafe impl<T: Sync + Send> Sync for SusLock<T> {}
unsafe impl<T: Send> Send for SusLock<T> {}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for SusLock<T> {}

impl<T: 'static> Drop for SusLock<T> {
#[inline]
fn drop(&mut self) {
if self.initialized.load(Ordering::SeqCst) {
unsafe { (*self.data.get()).assume_init_drop() };
}
}
}

static AHASH_SEED: SusLock<Option<[u64; 4]>> = SusLock::new();
static AHASH_SEED: OnceCell<Option<[u64; 4]>> = OnceCell::new();

/// Set the hashing seed. This is used to hash functions etc.
///
Expand Down Expand Up @@ -222,7 +54,11 @@ static AHASH_SEED: SusLock<Option<[u64; 4]>> = SusLock::new();
/// ```
#[inline(always)]
pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> {
AHASH_SEED.init(new_seed)
#[cfg(feature = "std")]
return AHASH_SEED.set(new_seed);

#[cfg(not(feature = "std"))]
return AHASH_SEED.set(new_seed.into()).map_err(|err| *err);
}

/// Get the current hashing Seed.
Expand All @@ -235,9 +71,9 @@ pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>
#[inline]
#[must_use]
pub fn get_ahash_seed() -> &'static Option<[u64; 4]> {
if !AHASH_SEED.is_initialized() {
return &hashing_env::AHASH_SEED;
if let Some(seed) = AHASH_SEED.get() {
seed
} else {
&hashing_env::AHASH_SEED
}

AHASH_SEED.get().unwrap_or(&hashing_env::AHASH_SEED)
}
4 changes: 2 additions & 2 deletions src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl Engine {

#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
{
engine.module_resolver =
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
Expand All @@ -288,7 +288,7 @@ impl Engine {

// default print/debug implementations
#[cfg(not(feature = "no_std"))]
#[cfg(not(target_family = "wasm"))]
#[cfg(any(not(target_family = "wasm"), not(target_os = "unknown")))]
{
engine.print = Some(Box::new(|s| println!("{s}")));
engine.debug = Some(Box::new(|s, source, pos| match (source, pos) {
Expand Down
9 changes: 3 additions & 6 deletions src/eval/chaining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use super::{Caches, GlobalRuntimeState, Target};
use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment};
use crate::config::hashing::SusLock;
use crate::config::hashing::OnceCell;
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::types::dynamic::Union;
use crate::{
Expand All @@ -14,11 +14,7 @@ use std::hash::Hash;
use std::prelude::v1::*;

/// Function call hashes to index getters and setters.
///
/// # Safety
///
/// Uses the extremely unsafe [`SusLock`]. Change to [`OnceCell`] when it is stabilized.
static INDEXER_HASHES: SusLock<(u64, u64)> = SusLock::new();
static INDEXER_HASHES: OnceCell<(u64, u64)> = OnceCell::new();

/// Get the pre-calculated index getter/setter hashes.
#[inline(always)]
Expand All @@ -29,6 +25,7 @@ fn hash_idx() -> (u64, u64) {
calc_fn_hash(None, FN_IDX_GET, 2),
calc_fn_hash(None, FN_IDX_SET, 3),
)
.into()
})
}

Expand Down

0 comments on commit 26fd283

Please sign in to comment.