veryl-analyzer 0.21.0

A modern hardware description language
Documentation
use crate::BigUint;
use crate::HashSet;
use crate::analyzer_error::{AnalyzerError, UnevaluableValueKind};
use crate::attribute::EnumEncodingItem;
use crate::conv::utils::TypePosition;
use crate::conv::{self, Context, Conv};
use crate::ir::{self, IrResult};
use crate::symbol::{EnumMemberValue, EnumProperty, Symbol, SymbolKind};
use crate::symbol_table;
use num_traits::ToPrimitive;

pub fn resolve_enum(list: &[Symbol]) -> Vec<AnalyzerError> {
    let mut errors = Vec::new();
    let mut context = conv::Context::default();

    for symbol in list {
        let SymbolKind::Enum(r#enum) = &symbol.kind else {
            unreachable!();
        };
        let enum_width = eval_enum_width(&mut context, symbol, r#enum, &mut errors);

        let mut pre_value = None;
        let mut member_width = 0;
        let mut seen_values: HashSet<BigUint> = HashSet::default();
        // x/z values have no numeric `value()`, so they bypass `seen_values`.
        let mut seen_xz: HashSet<(BigUint, BigUint)> = HashSet::default();
        for id in &r#enum.members {
            let mut symbol = symbol_table::get(*id).unwrap();

            let value = eval_enum_member_value(
                &mut context,
                &symbol,
                r#enum,
                pre_value.as_ref(),
                enum_width,
                &mut member_width,
                &mut seen_xz,
                &mut errors,
            );
            if matches!(value, EnumMemberValue::UnevaluableValue) {
                errors.push(AnalyzerError::unevaluable_value(
                    UnevaluableValueKind::EnumVariant,
                    &symbol.token.into(),
                ));
            }

            if let Some(value) = value.value() {
                let width = value.bits() as usize;
                if enum_width > 0 && width > enum_width {
                    errors.push(AnalyzerError::too_large_enum_variant(
                        &symbol.token.to_string(),
                        value.to_isize().unwrap_or(isize::MAX),
                        enum_width,
                        &symbol.token.into(),
                    ));
                }
                member_width = member_width.max(width);
                if !seen_values.insert(value.clone()) {
                    errors.push(AnalyzerError::duplicate_enum_variant(
                        &symbol.token.to_string(),
                        &symbol.token.into(),
                    ));
                }
            }

            if let SymbolKind::EnumMember(mut member) = symbol.kind {
                member.value = value.clone();
                symbol.kind = SymbolKind::EnumMember(member);
                symbol_table::update(symbol);
            }

            pre_value = Some(value);
        }

        let mut symbol = symbol.clone();
        if let SymbolKind::Enum(mut r#enum) = symbol.kind {
            r#enum.width = 1.max(enum_width.max(member_width));
            symbol.kind = SymbolKind::Enum(r#enum);
            symbol_table::update(symbol);
        }
    }

    errors.append(&mut context.drain_errors());

    errors
}

fn eval_enum_width(
    context: &mut Context,
    symbol: &Symbol,
    r#enum: &EnumProperty,
    errors: &mut Vec<AnalyzerError>,
) -> usize {
    let Some(r#type) = &r#enum.r#type else {
        return 0;
    };

    let width = if let Ok(x) = r#type.to_ir_type(context, TypePosition::Enum) {
        x.total_width().unwrap_or(0)
    } else {
        0
    };

    if width > 0 && calc_width(r#enum.members.len() - 1) > width {
        errors.push(AnalyzerError::too_much_enum_variant(
            &symbol.token.to_string(),
            r#enum.members.len(),
            width,
            &symbol.token.into(),
        ));
    }

    width
}

#[allow(clippy::too_many_arguments)]
fn eval_enum_member_value(
    context: &mut Context,
    symbol: &Symbol,
    r#enum: &EnumProperty,
    pre_value: Option<&EnumMemberValue>,
    enum_width: usize,
    member_width: &mut usize,
    seen_xz: &mut HashSet<(BigUint, BigUint)>,
    errors: &mut Vec<AnalyzerError>,
) -> EnumMemberValue {
    let SymbolKind::EnumMember(enum_member) = &symbol.kind else {
        unreachable!();
    };

    if let EnumMemberValue::ExplicitValue(ref expression, _) = enum_member.value {
        let Ok(mut expr): IrResult<ir::Expression> = Conv::conv(context, expression) else {
            return EnumMemberValue::UnevaluableValue;
        };

        let comptime = expr.eval_comptime(context, None);
        let Ok(value) = comptime.get_value() else {
            return EnumMemberValue::UnevaluableValue;
        };

        if value.is_xz() {
            if matches!(
                &r#enum.encoding,
                EnumEncodingItem::OneHot | EnumEncodingItem::Gray
            ) {
                EnumMemberValue::UnevaluableValue
            } else {
                if let Some(r#type) = &r#enum.r#type
                    && r#type.kind.is_2state()
                {
                    let src = comptime.r#type.to_string();
                    let dst = r#type.to_string();
                    errors.push(AnalyzerError::mismatch_assignment(
                        &src,
                        &dst,
                        &expression.into(),
                        &[],
                    ));
                }
                // `value()` is None for x/z, bypassing the normal width checks,
                // so validate the x/z literal's own width here instead.
                let needed = value.payload().bits().max(value.mask_xz().bits()) as usize;
                if enum_width > 0 && needed > enum_width {
                    errors.push(AnalyzerError::too_large_enum_variant(
                        &symbol.token.to_string(),
                        value.payload().to_isize().unwrap_or(isize::MAX),
                        enum_width,
                        &symbol.token.into(),
                    ));
                }
                *member_width = (*member_width).max(needed);
                // Key on (payload, x/z-mask): z sets payload bits under the mask,
                // so x and z differ.
                let key = (value.payload().into_owned(), value.mask_xz().into_owned());
                if !seen_xz.insert(key) {
                    errors.push(AnalyzerError::duplicate_enum_variant(
                        &symbol.token.to_string(),
                        &symbol.token.into(),
                    ));
                }
                enum_member.value.clone()
            }
        } else {
            let value = value.payload().into_owned();
            let is_valid = match r#enum.encoding {
                EnumEncodingItem::OneHot => value.count_ones() == 1,
                EnumEncodingItem::Gray => get_enum_member_next_value(r#enum.encoding, pre_value)
                    .map(|x| value == x)
                    .unwrap_or(true),
                _ => true,
            };
            if is_valid {
                EnumMemberValue::ExplicitValue(expression.clone(), Some(value))
            } else {
                errors.push(AnalyzerError::invalid_enum_variant(
                    &symbol.token.to_string(),
                    &r#enum.encoding.to_string(),
                    &symbol.token.into(),
                ));
                enum_member.value.clone()
            }
        }
    } else if let Some(value) = get_enum_member_next_value(r#enum.encoding, pre_value) {
        EnumMemberValue::ImplicitValue(value)
    } else {
        EnumMemberValue::UnevaluableValue
    }
}

fn calc_width(value: usize) -> usize {
    (usize::BITS - value.leading_zeros()) as usize
}

fn get_enum_member_next_value(
    encoding: EnumEncodingItem,
    pre_value: Option<&EnumMemberValue>,
) -> Option<BigUint> {
    if let Some(value) = pre_value
        && let Some(value) = value.value()
    {
        match encoding {
            EnumEncodingItem::Sequential => Some(value + 1u32),
            EnumEncodingItem::OneHot => Some(value << 1u32),
            EnumEncodingItem::Gray => {
                // `value` is the previous member's gray-encoded value, not its
                // ordinal index. Recover the ordinal by gray-decoding, then
                // re-encode the next ordinal so the chain stays a valid Gray
                // sequence (0, 1, 3, 2, 6, 7, 5, 4, ...).
                let mut n = value.clone();
                let mut g = value >> 1u32;
                while g != BigUint::from(0u32) {
                    n ^= &g;
                    g >>= 1u32;
                }
                let m = n + 1u32;
                let half = &m >> 1u32;
                Some(&m ^ &half)
            }
        }
    } else if pre_value.is_none() {
        if matches!(encoding, EnumEncodingItem::OneHot) {
            Some(BigUint::from(1u32))
        } else {
            Some(BigUint::from(0u32))
        }
    } else {
        None
    }
}