fix: broadcast_message now works — use read_volatile for children state
Browse filesThe pure Rust CTask::HandleMessage port (broadcast_message) was failing
because LLVM cached children_watermark and children_data reads across
virtual dispatch calls. Child handlers can modify the parent's children
array (add/remove/realloc), so these fields must be re-read each
iteration.
Fix: use read_volatile via core::ptr::addr_of! for both fields.
This matches the original WA code which re-reads [EDI+0x10] and
[EDI+0x14] from memory each iteration.
CTaskCloud and CTaskFilter handlers now use broadcast_message directly
instead of calling the original WA CTask::HandleMessage at 0x562F30.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crates/openwa-core/src/task/base.rs
CHANGED
|
@@ -208,49 +208,46 @@ pub unsafe trait Task {
|
|
| 208 |
/// # Safety
|
| 209 |
/// All non-null children must be valid CTask pointers with valid vtables.
|
| 210 |
unsafe fn broadcast_message(
|
| 211 |
-
&self,
|
| 212 |
sender: *mut CTask,
|
| 213 |
msg_type: u32,
|
| 214 |
size: u32,
|
| 215 |
data: *const u8,
|
| 216 |
) {
|
| 217 |
-
let task_ptr = self.
|
| 218 |
-
|
| 219 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
let mut i: usize = 0;
|
| 221 |
-
let mut child = core::ptr::null_mut::<CTask>();
|
| 222 |
loop {
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
i += 1;
|
| 229 |
-
if !child.is_null() {
|
| 230 |
-
break;
|
| 231 |
-
}
|
| 232 |
-
}
|
| 233 |
-
|
| 234 |
-
while !child.is_null() {
|
| 235 |
-
// Dispatch via typed CTaskVtable — every task's vtable starts
|
| 236 |
-
// with the same 8-slot layout, so this cast is always valid.
|
| 237 |
-
let vt = &*((*child).vtable as *const CTaskVtable);
|
| 238 |
-
(vt.handle_message)(child, sender, msg_type, size, data);
|
| 239 |
-
|
| 240 |
-
// Find next non-null child — re-read watermark each time
|
| 241 |
-
// because the handler may have added or removed siblings
|
| 242 |
-
child = core::ptr::null_mut();
|
| 243 |
-
loop {
|
| 244 |
-
let watermark = (*task_ptr).children_watermark as usize;
|
| 245 |
if i >= watermark {
|
| 246 |
-
|
| 247 |
}
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
| 249 |
i += 1;
|
| 250 |
-
if !
|
| 251 |
-
break;
|
| 252 |
}
|
| 253 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
}
|
| 255 |
}
|
| 256 |
}
|
|
|
|
| 208 |
/// # Safety
|
| 209 |
/// All non-null children must be valid CTask pointers with valid vtables.
|
| 210 |
unsafe fn broadcast_message(
|
| 211 |
+
&mut self,
|
| 212 |
sender: *mut CTask,
|
| 213 |
msg_type: u32,
|
| 214 |
size: u32,
|
| 215 |
data: *const u8,
|
| 216 |
) {
|
| 217 |
+
let task_ptr = self.as_task_ptr_mut();
|
| 218 |
+
|
| 219 |
+
// Scan for non-null children and dispatch HandleMessage.
|
| 220 |
+
// Mirrors WA's CTask::HandleMessage at 0x562F30 exactly:
|
| 221 |
+
// scan → dispatch → re-read watermark → scan next → ...
|
| 222 |
+
//
|
| 223 |
+
// IMPORTANT: read_volatile is required for watermark and children_data
|
| 224 |
+
// because child handlers may modify this task's children array
|
| 225 |
+
// (add/remove children, realloc the array). LLVM would otherwise cache
|
| 226 |
+
// these reads across the virtual dispatch call.
|
| 227 |
let mut i: usize = 0;
|
|
|
|
| 228 |
loop {
|
| 229 |
+
// Scan for next non-null child
|
| 230 |
+
let child = loop {
|
| 231 |
+
let watermark = core::ptr::read_volatile(
|
| 232 |
+
core::ptr::addr_of!((*task_ptr).children_watermark),
|
| 233 |
+
) as usize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
if i >= watermark {
|
| 235 |
+
return;
|
| 236 |
}
|
| 237 |
+
let children = core::ptr::read_volatile(
|
| 238 |
+
core::ptr::addr_of!((*task_ptr).children_data),
|
| 239 |
+
);
|
| 240 |
+
let c = *children.add(i);
|
| 241 |
i += 1;
|
| 242 |
+
if !c.is_null() {
|
| 243 |
+
break c;
|
| 244 |
}
|
| 245 |
+
};
|
| 246 |
+
|
| 247 |
+
// Dispatch via CTaskVtable — every task's vtable starts with
|
| 248 |
+
// the same 8-slot base layout, so this cast is always valid.
|
| 249 |
+
let vt = &*((*child).vtable as *const CTaskVtable);
|
| 250 |
+
(vt.handle_message)(child, sender, msg_type, size, data);
|
| 251 |
}
|
| 252 |
}
|
| 253 |
}
|
crates/openwa-wormkit/src/replacements/task/cloud.rs
CHANGED
|
@@ -96,10 +96,8 @@ unsafe extern "thiscall" fn cloud_handle_message(
|
|
| 96 |
_ => {}
|
| 97 |
}
|
| 98 |
|
| 99 |
-
// Broadcast to children
|
| 100 |
-
|
| 101 |
-
core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
|
| 102 |
-
base_handler(cloud.as_task_ptr_mut(), sender, msg_type, size, data);
|
| 103 |
}
|
| 104 |
|
| 105 |
pub fn install() -> Result<(), String> {
|
|
|
|
| 96 |
_ => {}
|
| 97 |
}
|
| 98 |
|
| 99 |
+
// Broadcast to children (pure Rust port of CTask::HandleMessage)
|
| 100 |
+
cloud.broadcast_message(sender, msg_type, size, data);
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
|
| 103 |
pub fn install() -> Result<(), String> {
|
crates/openwa-wormkit/src/replacements/task/filter.rs
CHANGED
|
@@ -29,10 +29,8 @@ unsafe extern "thiscall" fn filter_handle_message(
|
|
| 29 |
return; // message not subscribed — drop silently
|
| 30 |
}
|
| 31 |
|
| 32 |
-
// Broadcast to children
|
| 33 |
-
|
| 34 |
-
core::mem::transmute(openwa_core::rebase::rb(va::CTASK_VT2_HANDLE_MESSAGE) as usize);
|
| 35 |
-
base_handler(filter.as_task_ptr() as *mut CTask, sender, msg_type, size, data);
|
| 36 |
}
|
| 37 |
|
| 38 |
pub fn install() -> Result<(), String> {
|
|
|
|
| 29 |
return; // message not subscribed — drop silently
|
| 30 |
}
|
| 31 |
|
| 32 |
+
// Broadcast to children (pure Rust port of CTask::HandleMessage)
|
| 33 |
+
(*this).broadcast_message(sender, msg_type, size, data);
|
|
|
|
|
|
|
| 34 |
}
|
| 35 |
|
| 36 |
pub fn install() -> Result<(), String> {
|