Files
hakorune/src/mir/effect.rs

402 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* MIR Effect System - Track side effects for optimization
*
* Based on ChatGPT5's design for parallel execution and optimization safety
*/
use crate::debug::log as dlog;
use std::fmt;
/// Effect flags for tracking side effects and enabling optimizations
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EffectMask(u16);
/// Individual effect types for the 4-category MIR hierarchy
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Effect {
/// Pure computation with no side effects (Tier-0: reorderable, CSE/LICM eligible)
Pure = 0x0001,
/// Mutable operations (Tier-1: same Box/Field dependency preservation)
Mut = 0x0002,
/// I/O operations (Tier-1: no reordering, side effects present)
Io = 0x0004,
/// Control flow operations (Tier-0: affects execution flow)
Control = 0x0008,
// Legacy effects for compatibility (will be mapped to above categories)
/// Reads from heap/memory (maps to Pure if read-only)
ReadHeap = 0x0010,
/// Writes to heap/memory (maps to Mut)
WriteHeap = 0x0020,
/// P2P/network communication (maps to Io)
P2P = 0x0040,
/// Foreign Function Interface calls (maps to Io)
FFI = 0x0080,
/// May panic or throw exceptions (maps to Io)
Panic = 0x0100,
/// Allocates memory (maps to Mut)
Alloc = 0x0200,
/// Accesses global state (maps to Io)
Global = 0x0400,
/// Thread/async operations (maps to Io)
Async = 0x0800,
/// Unsafe operations (maps to Io)
Unsafe = 0x1000,
/// Debug/logging operations (maps to Io)
Debug = 0x2000,
/// Memory barrier operations (maps to Io)
Barrier = 0x4000,
}
impl EffectMask {
/// No effects - pure computation
pub const PURE: Self = Self(Effect::Pure as u16);
/// Mutable operations (writes, ownership changes)
pub const MUT: Self = Self(Effect::Mut as u16);
/// I/O operations (external effects, cannot reorder)
pub const IO: Self = Self(Effect::Io as u16);
/// Control flow operations
pub const CONTROL: Self = Self(Effect::Control as u16);
// Legacy constants for compatibility
/// Memory read effects
pub const READ: Self = Self(Effect::ReadHeap as u16);
pub const READ_ALIAS: Self = Self::READ; // Uppercase alias for compatibility
/// Memory write effects (includes read)
pub const WRITE: Self = Self((Effect::WriteHeap as u16) | (Effect::ReadHeap as u16));
/// P2P communication effects
pub const P2P: Self = Self(Effect::P2P as u16);
/// Panic/exception effects
pub const PANIC: Self = Self(Effect::Panic as u16);
/// All effects - maximum side effects
pub const ALL: Self = Self(0xFFFF);
/// Create an empty effect mask
pub fn new() -> Self {
Self(0)
}
/// Create effect mask from raw bits
pub fn from_bits(bits: u16) -> Self {
Self(bits)
}
/// Get raw bits
pub fn bits(self) -> u16 {
self.0
}
/// Add an effect to the mask
pub fn add(self, effect: Effect) -> Self {
Self(self.0 | (effect as u16))
}
/// Remove an effect from the mask
pub fn remove(self, effect: Effect) -> Self {
Self(self.0 & !(effect as u16))
}
/// Check if mask contains an effect
pub fn contains(self, effect: Effect) -> bool {
(self.0 & (effect as u16)) != 0
}
/// Check if mask contains any of the given effects
pub fn contains_any(self, mask: EffectMask) -> bool {
(self.0 & mask.0) != 0
}
/// Check if mask contains all of the given effects
pub fn contains_all(self, mask: EffectMask) -> bool {
(self.0 & mask.0) == mask.0
}
/// Combine two effect masks
pub fn union(self, other: EffectMask) -> Self {
Self(self.0 | other.0)
}
/// Get intersection of two effect masks
pub fn intersection(self, other: EffectMask) -> Self {
Self(self.0 & other.0)
}
/// Check if the computation is pure (no side effects)
pub fn is_pure(self) -> bool {
// READ/WRITE/IO/CONTROLがあれば純粋ではないREADはreadonly扱い
let pure = !self.contains(Effect::ReadHeap)
&& !self.is_mut()
&& !self.is_io()
&& !self.is_control();
if dlog::on("NYASH_DEBUG_EFFECTS") {
eprintln!(
"[EFFECT] bits={:#06x} primary={:?} is_pure={} read_only={} mut={} io={}",
self.bits(),
self.primary_category(),
pure,
self.is_read_only(),
self.is_mut(),
self.is_io()
);
}
pure
}
/// Check if the computation is mutable (modifies state)
pub fn is_mut(self) -> bool {
self.contains(Effect::Mut)
|| self.contains(Effect::WriteHeap)
|| self.contains(Effect::Alloc)
}
/// Check if the computation has I/O effects (external side effects)
pub fn is_io(self) -> bool {
self.contains(Effect::Io)
|| self.contains(Effect::P2P)
|| self.contains(Effect::FFI)
|| self.contains(Effect::Global)
|| self.contains(Effect::Async)
|| self.contains(Effect::Unsafe)
|| self.contains(Effect::Debug)
|| self.contains(Effect::Barrier)
|| self.contains(Effect::Panic)
}
/// Check if the computation affects control flow
pub fn is_control(self) -> bool {
self.contains(Effect::Control)
}
/// Get the primary effect category for MIR optimization
pub fn primary_category(self) -> Effect {
if self.is_control() {
Effect::Control
} else if self.is_io() {
Effect::Io
} else if self.is_mut() {
Effect::Mut
} else {
Effect::Pure
}
}
/// Check if the computation only reads (doesn't modify state)
pub fn is_read_only(self) -> bool {
!self.is_mut() && !self.is_io()
}
/// Check if parallel execution is safe
pub fn is_parallel_safe(self) -> bool {
!self.contains(Effect::WriteHeap)
&& !self.contains(Effect::Global)
&& !self.contains(Effect::Unsafe)
}
/// Check if operation can be moved across other operations
pub fn is_moveable(self) -> bool {
self.is_pure() || self.is_read_only()
}
/// Get a human-readable list of effects
pub fn effect_names(self) -> Vec<&'static str> {
let mut names = Vec::new();
// Primary categories
if self.contains(Effect::Pure) {
names.push("pure");
}
if self.contains(Effect::Mut) {
names.push("mut");
}
if self.contains(Effect::Io) {
names.push("io");
}
if self.contains(Effect::Control) {
names.push("control");
}
// Legacy effects for detailed tracking
if self.contains(Effect::ReadHeap) {
names.push("read");
}
if self.contains(Effect::WriteHeap) {
names.push("write");
}
if self.contains(Effect::P2P) {
names.push("p2p");
}
if self.contains(Effect::FFI) {
names.push("ffi");
}
if self.contains(Effect::Panic) {
names.push("panic");
}
if self.contains(Effect::Alloc) {
names.push("alloc");
}
if self.contains(Effect::Global) {
names.push("global");
}
if self.contains(Effect::Async) {
names.push("async");
}
if self.contains(Effect::Unsafe) {
names.push("unsafe");
}
if self.contains(Effect::Debug) {
names.push("debug");
}
if self.contains(Effect::Barrier) {
names.push("barrier");
}
if names.is_empty() {
names.push("none");
}
names
}
}
impl Default for EffectMask {
fn default() -> Self {
Self::PURE
}
}
impl fmt::Display for EffectMask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let names = self.effect_names();
if names.is_empty() {
write!(f, "none")
} else {
write!(f, "{}", names.join("|"))
}
}
}
impl std::ops::BitOr for EffectMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl std::ops::BitOrAssign for EffectMask {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
}
impl std::ops::BitAnd for EffectMask {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
self.intersection(rhs)
}
}
impl std::ops::BitAndAssign for EffectMask {
fn bitand_assign(&mut self, rhs: Self) {
*self = *self & rhs;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_effect_mask_creation() {
let pure = EffectMask::PURE;
let read = EffectMask::READ;
let write = EffectMask::WRITE;
assert!(pure.is_pure());
assert!(!read.is_pure());
assert!(!write.is_pure());
assert!(read.is_read_only());
assert!(!write.is_read_only());
}
#[test]
fn test_effect_combination() {
let mut effects = EffectMask::new();
assert!(effects.is_pure());
effects = effects.add(Effect::ReadHeap);
assert!(effects.contains(Effect::ReadHeap));
assert!(effects.is_read_only());
effects = effects.add(Effect::WriteHeap);
assert!(effects.contains(Effect::WriteHeap));
assert!(!effects.is_read_only());
effects = effects.add(Effect::Io);
assert!(effects.contains(Effect::Io));
assert!(!effects.is_parallel_safe());
}
#[test]
fn test_effect_union() {
let read_effect = EffectMask::READ;
let io_effect = EffectMask::IO;
let combined = read_effect | io_effect;
assert!(combined.contains(Effect::ReadHeap));
assert!(combined.contains(Effect::Io));
assert!(!combined.is_pure());
// IO + read remains parallel-safe under current semantics
assert!(combined.is_parallel_safe());
}
#[test]
fn test_parallel_safety() {
let pure = EffectMask::PURE;
let read = EffectMask::READ;
let write = EffectMask::WRITE;
let io = EffectMask::IO;
assert!(pure.is_parallel_safe());
assert!(read.is_parallel_safe());
assert!(!write.is_parallel_safe());
assert!(io.is_parallel_safe()); // I/O can be parallel if properly synchronized
}
#[test]
fn test_effect_names() {
let pure = EffectMask::PURE;
assert_eq!(pure.effect_names(), vec!["pure"]);
let read_write = EffectMask::READ.add(Effect::WriteHeap);
let names = read_write.effect_names();
assert!(names.contains(&"read"));
assert!(names.contains(&"write"));
}
#[test]
fn test_effect_display() {
let pure = EffectMask::PURE;
assert_eq!(format!("{}", pure), "pure");
let read_io = EffectMask::READ | EffectMask::IO;
let display = format!("{}", read_io);
assert!(display.contains("read"));
assert!(display.contains("io"));
// is_pure should be false when IO is present, even if PURE bit was set initially
let mixed = EffectMask::PURE | EffectMask::IO;
assert!(!mixed.is_pure());
}
}