Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion hew-codegen/src/mlir/MLIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2021,7 +2021,8 @@ std::optional<mlir::Value> MLIRGen::generateBuiltinMethodCall(const ast::ExprMet
return true;
key = coerceType(key, keyType, location);
resultOut =
builder.create<hew::HashMapContainsKeyOp>(location, i32Type, mapValue, key).getResult();
builder.create<hew::HashMapContainsKeyOp>(location, builder.getI1Type(), mapValue, key)
.getResult();
return true;
}
if (method == "keys") {
Expand Down
68 changes: 4 additions & 64 deletions hew-runtime/src/actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use std::collections::HashSet;
use std::ffi::{c_int, c_void};
use std::ptr;
use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU64, Ordering};
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;

use crate::internal::types::HewActorState;
Expand Down Expand Up @@ -41,6 +40,7 @@ pub(crate) fn set_current_actor(actor: *mut HewActor) -> *mut HewActor {

/// Set the current actor, returning the previous value.
#[cfg(target_arch = "wasm32")]
#[allow(dead_code)]
pub(crate) fn set_current_actor(actor: *mut HewActor) -> *mut HewActor {
// SAFETY: WASM is single-threaded, no data races possible.
unsafe {
Expand Down Expand Up @@ -249,15 +249,9 @@ struct ActorPtr(*mut HewActor);
unsafe impl Send for ActorPtr {}

/// Set of all live (not-yet-freed) actor pointers.
#[cfg(not(target_arch = "wasm32"))]
static LIVE_ACTORS: Mutex<Option<HashSet<ActorPtr>>> = Mutex::new(None);

#[cfg(target_arch = "wasm32")]
// SAFETY: WASM is single-threaded. Using a static mut with manual access control.
static mut LIVE_ACTORS_WASM: Option<HashSet<ActorPtr>> = None;

/// Register an actor in the live tracking set.
#[cfg(not(target_arch = "wasm32"))]
fn track_actor(actor: *mut HewActor) {
if let Ok(mut guard) = LIVE_ACTORS.lock() {
guard
Expand All @@ -266,22 +260,10 @@ fn track_actor(actor: *mut HewActor) {
}
}

/// Register an actor in the live tracking set.
#[cfg(target_arch = "wasm32")]
fn track_actor(actor: *mut HewActor) {
// SAFETY: WASM is single-threaded, no data races possible.
unsafe {
LIVE_ACTORS_WASM
.get_or_insert_with(HashSet::new)
.insert(ActorPtr(actor));
}
}

/// Remove an actor from the live tracking set.
///
/// Returns `true` if the actor was present and removed, `false` if it
/// was not found (e.g. already consumed by [`cleanup_all_actors`]).
#[cfg(not(target_arch = "wasm32"))]
fn untrack_actor(actor: *mut HewActor) -> bool {
if let Ok(mut guard) = LIVE_ACTORS.lock() {
if let Some(set) = guard.as_mut() {
Expand All @@ -291,29 +273,13 @@ fn untrack_actor(actor: *mut HewActor) -> bool {
false
}

/// Remove an actor from the live tracking set.
///
/// Returns `true` if the actor was present and removed, `false` if it
/// was not found (e.g. already consumed by [`cleanup_all_actors`]).
#[cfg(target_arch = "wasm32")]
fn untrack_actor(actor: *mut HewActor) -> bool {
// SAFETY: WASM is single-threaded, no data races possible.
unsafe {
if let Some(set) = LIVE_ACTORS_WASM.as_mut() {
return set.remove(&ActorPtr(actor));
}
}
false
}

/// Free all remaining tracked actors. Called during scheduler shutdown
/// after all worker threads have been joined.
///
/// # Safety
///
/// Must only be called after all worker threads have stopped — no
/// concurrent dispatch can be in progress.
#[cfg(not(target_arch = "wasm32"))]
/// Must only be called after all worker threads have stopped (native)
/// or when no dispatch is in progress (WASM).
pub(crate) unsafe fn cleanup_all_actors() {
let actors = {
let mut guard = match LIVE_ACTORS.lock() {
Expand All @@ -330,33 +296,7 @@ pub(crate) unsafe fn cleanup_all_actors() {
if actor.is_null() {
continue;
}
// SAFETY: All workers have been joined, so no actor can be Running or Runnable.
// SAFETY: The actor was allocated by a spawn function and has not been freed yet.
unsafe { free_actor_resources(actor) };
}
}

/// Free all remaining tracked actors. Called during WASM scheduler
/// shutdown.
///
/// # Safety
///
/// Must only be called when no dispatch is in progress.
#[cfg(target_arch = "wasm32")]
pub(crate) unsafe fn cleanup_all_actors() {
// SAFETY: WASM is single-threaded, no data races possible.
let actors = unsafe {
match LIVE_ACTORS_WASM.as_mut() {
Some(set) => std::mem::take(set),
None => HashSet::new(),
}
};

for ActorPtr(actor) in actors {
if actor.is_null() {
continue;
}
// SAFETY: No dispatch is in progress.
// SAFETY: Caller guarantees no concurrent dispatch.
// SAFETY: The actor was allocated by a spawn function and has not been freed yet.
unsafe { free_actor_resources(actor) };
}
Expand Down
26 changes: 6 additions & 20 deletions hew-runtime/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,12 @@
//! }
//! ```

use std::cell::UnsafeCell;
use std::collections::{HashMap, VecDeque};
use std::ffi::{c_void, CString};
use std::sync::{Mutex, MutexGuard, OnceLock};

use crate::actor::HewActor;

/// Single-threaded cell for WASM-only statics.
///
/// SAFETY: Only safe in single-threaded contexts (e.g., WASM modules).
struct WasmCell<T>(UnsafeCell<T>);
// SAFETY: WASM is single-threaded; no concurrent access.
unsafe impl<T> Sync for WasmCell<T> {}
impl<T> WasmCell<T> {
const fn new(val: T) -> Self {
Self(UnsafeCell::new(val))
}
/// # Safety
/// Caller must ensure no concurrent access (single-threaded WASM).
#[allow(clippy::mut_from_ref)]
unsafe fn get_mut(&self) -> &mut T {
// SAFETY: Caller guarantees single-threaded access.
unsafe { &mut *self.0.get() }
}
}

// ── Outbound message queue ──────────────────────────────────────────────

/// An outbound message emitted by an actor for the host.
Expand Down Expand Up @@ -273,7 +253,10 @@ pub unsafe extern "C" fn hew_wasm_send(
const MAILBOX_OFFSET: usize = std::mem::offset_of!(HewActor, mailbox);

// Verify offsets match expectations (checked at compile time).
#[cfg(target_pointer_width = "64")]
const _: () = assert!(MAILBOX_OFFSET == 48);
#[cfg(target_pointer_width = "32")]
const _: () = assert!(MAILBOX_OFFSET == 36);

// SAFETY: actor_ptr is a valid HewActor pointer from the registry.
let mailbox_ptr = unsafe {
Expand All @@ -298,7 +281,10 @@ pub unsafe extern "C" fn hew_wasm_send(
const ACTOR_STATE_OFFSET: usize = std::mem::offset_of!(HewActor, actor_state);

// Verify offsets match expectations (checked at compile time).
#[cfg(target_pointer_width = "64")]
const _: () = assert!(ACTOR_STATE_OFFSET == 56);
#[cfg(target_pointer_width = "32")]
const _: () = assert!(ACTOR_STATE_OFFSET == 40);

const IDLE: i32 = 0; // HewActorState::Idle
const RUNNABLE: i32 = 1; // HewActorState::Runnable
Expand Down
6 changes: 3 additions & 3 deletions hew-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ pub mod profiler {
pub fn maybe_start() {}
/// No-op: profiler feature is disabled.
pub fn maybe_start_with_context(
_cluster: *mut crate::cluster::HewCluster,
_connmgr: *mut crate::connection::HewConnMgr,
_routing: *mut crate::routing::HewRoutingTable,
_cluster: *mut std::ffi::c_void,
_connmgr: *mut std::ffi::c_void,
_routing: *mut std::ffi::c_void,
) {
}
/// No-op: profiler feature is disabled.
Expand Down
57 changes: 34 additions & 23 deletions hew-runtime/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,21 @@ pub use native::*;
mod wasm {
use std::collections::HashMap;
use std::ffi::{c_char, c_void, CStr};
use std::sync::Mutex;

/// Simple single-threaded registry for WASM.
/// SAFETY: WASM is single-threaded, no data races possible.
static mut REGISTRY: Option<HashMap<String, *mut c_void>> = None;
/// Wrapper around `*mut c_void` that implements Send.
/// SAFETY: WASM is single-threaded, no cross-thread sharing.
struct SendPtr(*mut c_void);
unsafe impl Send for SendPtr {}

fn get_or_init() -> &'static mut HashMap<String, *mut c_void> {
// SAFETY: WASM is single-threaded, no concurrent access.
unsafe { REGISTRY.get_or_insert_with(HashMap::new) }
static REGISTRY: Mutex<Option<HashMap<String, SendPtr>>> = Mutex::new(None);

fn with_registry<F, R>(f: F) -> R
where
F: FnOnce(&mut HashMap<String, SendPtr>) -> R,
{
let mut guard = REGISTRY.lock().unwrap_or_else(|e| e.into_inner());
f(guard.get_or_insert_with(HashMap::new))
}

/// Register an actor by name.
Expand All @@ -226,12 +233,13 @@ mod wasm {
let key = unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.into_owned();
let reg = get_or_init();
if reg.contains_key(&key) {
return -1;
}
reg.insert(key, actor);
0
with_registry(|reg| {
if reg.contains_key(&key) {
return -1;
}
reg.insert(key, SendPtr(actor));
0
})
}

/// Look up an actor by name.
Expand All @@ -249,10 +257,11 @@ mod wasm {
// SAFETY: `name` was checked for null above and caller guarantees it points
// to a valid NUL-terminated C string.
let key = unsafe { CStr::from_ptr(name) }.to_string_lossy();
get_or_init()
.get(key.as_ref())
.copied()
.unwrap_or(std::ptr::null_mut())
with_registry(|reg| {
reg.get(key.as_ref())
.map(|p| p.0)
.unwrap_or(std::ptr::null_mut())
})
}

/// Remove an actor registration by name.
Expand All @@ -270,23 +279,25 @@ mod wasm {
// SAFETY: `name` was checked for null above and caller guarantees it points
// to a valid NUL-terminated C string.
let key = unsafe { CStr::from_ptr(name) }.to_string_lossy();
if get_or_init().remove(key.as_ref()).is_some() {
0
} else {
-1
}
with_registry(|reg| {
if reg.remove(key.as_ref()).is_some() {
0
} else {
-1
}
})
}

/// Return the number of registered actors.
#[no_mangle]
pub extern "C" fn hew_registry_count() -> i32 {
get_or_init().len() as i32
with_registry(|reg| reg.len() as i32)
}

/// Remove all entries from the registry.
#[no_mangle]
pub extern "C" fn hew_registry_clear() {
get_or_init().clear();
with_registry(|reg| reg.clear());
}
}

Expand Down
1 change: 1 addition & 0 deletions hew-runtime/src/reply_channel_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::ptr;
/// On WASM, the ask pattern is cooperative: the caller sends a message,
/// runs the scheduler until the dispatch function calls [`hew_reply`],
/// then reads the reply synchronously.
#[derive(Debug)]
#[repr(C)]
pub struct WasmReplyChannel {
/// Reply payload (malloc'd by [`hew_reply`], owned by the waiter).
Expand Down
2 changes: 2 additions & 0 deletions hew-runtime/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1119,6 +1119,7 @@ pub unsafe extern "C" fn hew_vec_reverse_i32(v: *mut HewVec) {
/// # Safety
///
/// `v` must be a valid, non-null pointer to a `HewVec` with i32 element size.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) unsafe fn hwvec_to_u8(v: *mut HewVec) -> Vec<u8> {
cabi_guard!(v.is_null(), Vec::new());
// SAFETY: caller guarantees v is a valid HewVec.
Expand All @@ -1139,6 +1140,7 @@ pub(crate) unsafe fn hwvec_to_u8(v: *mut HewVec) -> Vec<u8> {
/// # Safety
///
/// None — all memory is managed by the runtime allocator.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) unsafe fn u8_to_hwvec(data: &[u8]) -> *mut HewVec {
// SAFETY: hew_vec_new allocates a valid HewVec.
let v = unsafe { hew_vec_new() };
Expand Down
Loading