yulang-runtime 0.1.1

Runtime IR, validation, monomorphization, and VM support for Yulang
Documentation
use super::*;

const PERSISTENT_VECTOR_CHUNK: usize = 32;

impl<T> Default for PersistentVector<T> {
    fn default() -> Self {
        Self { len: 0, tail: None }
    }
}

impl<T: Clone> PersistentVector<T> {
    pub(super) fn push(&self, item: T) -> Self {
        if let Some(tail) = &self.tail
            && tail.items.len() < PERSISTENT_VECTOR_CHUNK
        {
            let mut items = tail.items.iter().cloned().collect::<Vec<_>>();
            items.push(item);
            return Self {
                len: self.len + 1,
                tail: Some(Rc::new(PersistentVectorChunk {
                    items: Rc::from(items.into_boxed_slice()),
                    parent: tail.parent.clone(),
                })),
            };
        }

        Self {
            len: self.len + 1,
            tail: Some(Rc::new(PersistentVectorChunk {
                items: Rc::from(vec![item].into_boxed_slice()),
                parent: self.tail.clone(),
            })),
        }
    }

    pub(super) fn last(&self) -> Option<&T> {
        self.tail.as_ref().and_then(|chunk| chunk.items.last())
    }

    pub(super) fn any_chunk_newest(&self, mut f: impl FnMut(&[T]) -> bool) -> bool {
        let mut cursor = self.tail.as_ref();
        while let Some(chunk) = cursor {
            if f(&chunk.items) {
                return true;
            }
            cursor = chunk.parent.as_ref();
        }
        false
    }

    pub(super) fn find_map_newest<R>(&self, mut f: impl FnMut(&[T]) -> Option<R>) -> Option<R> {
        let mut cursor = self.tail.as_ref();
        while let Some(chunk) = cursor {
            if let Some(value) = f(&chunk.items) {
                return Some(value);
            }
            cursor = chunk.parent.as_ref();
        }
        None
    }

    fn items_oldest(&self) -> Vec<T> {
        let mut chunks = Vec::new();
        let mut cursor = self.tail.as_ref();
        while let Some(chunk) = cursor {
            chunks.push(chunk.clone());
            cursor = chunk.parent.as_ref();
        }
        let mut out = Vec::with_capacity(self.len);
        for chunk in chunks.into_iter().rev() {
            out.extend(chunk.items.iter().cloned());
        }
        out
    }
}

impl GuardStack {
    pub(super) fn push(&self, entry: GuardEntry) -> Self {
        Self(self.0.push(entry))
    }

    pub(super) fn peek(&self) -> Option<u64> {
        self.0.last().map(|entry| entry.id)
    }

    pub(super) fn contains(&self, id: u64) -> bool {
        self.0
            .any_chunk_newest(|chunk| chunk.binary_search_by_key(&id, |entry| entry.id).is_ok())
    }

    pub(super) fn find_var(&self, var: EffectIdVar) -> Option<u64> {
        self.0.find_map_newest(|chunk| {
            chunk
                .iter()
                .rev()
                .find(|entry| entry.var == var)
                .map(|entry| entry.id)
        })
    }

    pub(super) fn overlay_newer(&self, newer: &Self) -> Self {
        newer
            .0
            .items_oldest()
            .into_iter()
            .fold(self.clone(), |stack, entry| {
                if stack.find_var(entry.var).is_some() {
                    stack
                } else {
                    stack.push(entry)
                }
            })
    }
}

#[cfg_attr(not(test), allow(dead_code))]
pub(super) fn mark_request(request: VmRequest, thunk: &VmThunk) -> VmRequest {
    mark_request_with_blocked(request, &thunk.blocked)
}

pub(super) fn mark_request_with_blocked(
    mut request: VmRequest,
    blocked_effects: &[BlockedEffect],
) -> VmRequest {
    for blocked in blocked_effects {
        if effect_is_allowed(&blocked.allowed, &request.effect) {
            continue;
        }
        if request
            .blocked_id
            .is_some_and(|blocked| request.continuation.guard_stack.contains(blocked))
        {
            continue;
        }
        request.blocked_id = Some(blocked.guard_id);
    }
    request
}

pub(super) fn mark_request_with_active_blocked(
    mut request: VmRequest,
    blocked_effects: &[BlockedEffect],
) -> VmRequest {
    for blocked in blocked_effects.iter().rev() {
        if effect_is_allowed(&blocked.allowed, &request.effect) {
            continue;
        }
        if request.blocked_id.is_some() {
            continue;
        }
        request.blocked_id = Some(blocked.guard_id);
    }
    request
}

pub(super) fn push_frame(mut request: VmRequest, frame: Frame) -> VmRequest {
    request.continuation.frames.insert(0, frame);
    request
}

pub(super) fn prepend_frames(continuation: &mut VmContinuation, mut frames: Vec<Frame>) {
    frames.append(&mut continuation.frames);
    continuation.frames = frames;
}

pub(super) fn effect_is_allowed(allowed: &typed_ir::Type, effect: &typed_ir::Path) -> bool {
    match allowed {
        typed_ir::Type::Any => true,
        typed_ir::Type::Never => false,
        typed_ir::Type::Named { path, .. } => effect_path_matches_allowed(path, effect),
        typed_ir::Type::Row { items, tail } => {
            items.iter().any(|item| effect_is_allowed(item, effect))
                || matches!(tail.as_ref(), typed_ir::Type::Any)
        }
        _ => false,
    }
}

pub(super) fn effect_path_matches_allowed(
    allowed: &typed_ir::Path,
    effect: &typed_ir::Path,
) -> bool {
    if effect.segments.starts_with(&allowed.segments) {
        return true;
    }
    if allowed.segments.len() > 1
        && effect.segments.len() == allowed.segments.len()
        && effect.segments[..effect.segments.len() - 1]
            == allowed.segments[..allowed.segments.len() - 1]
        && effect_segment_matches_allowed(
            &allowed.segments[allowed.segments.len() - 1],
            &effect.segments[effect.segments.len() - 1],
        )
    {
        return true;
    }
    effect
        .segments
        .iter()
        .enumerate()
        .skip(1)
        .any(|(index, _)| effect.segments[index..].starts_with(&allowed.segments))
}

fn effect_segment_matches_allowed(allowed: &typed_ir::Name, effect: &typed_ir::Name) -> bool {
    allowed == effect
        || effect
            .0
            .strip_suffix("#effect")
            .is_some_and(|base| base == allowed.0)
}

pub(super) fn is_float_type(ty: &typed_ir::Type) -> bool {
    matches!(ty, typed_ir::Type::Named { path, args } if args.is_empty() && path == &standard_float_path())
}

fn standard_float_path() -> typed_ir::Path {
    typed_ir::Path::from_name(typed_ir::Name("float".to_string()))
}