<commit_before>#![allow(unsafe_code)]

use std::{
    borrow::{Borrow, BorrowMut},
    convert::TryFrom,
    hash::{Hash, Hasher},
    mem::MaybeUninit,
    sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};

use crate::{
    atomic_shim::AtomicU64,
    debug_delay,
    dll::{DoublyLinkedList, Node},
    fastlock::FastLock,
    FastSet8, Guard, PageId,
};

#[cfg(any(test, feature = "lock_free_delays"))]
const MAX_QUEUE_ITEMS: usize = 4;

#[cfg(not(any(test, feature = "lock_free_delays")))]
const MAX_QUEUE_ITEMS: usize = 64;

#[cfg(any(test, feature = "lock_free_delays"))]
const N_SHARDS: usize = 2;

#[cfg(not(any(test, feature = "lock_free_delays")))]
const N_SHARDS: usize = 256;

struct AccessBlock {
    len: AtomicUsize,
    block: [AtomicU64; MAX_QUEUE_ITEMS],
    next: AtomicPtr<AccessBlock>,
}

impl Default for AccessBlock {
    fn default() -> AccessBlock {
        AccessBlock {
            len: AtomicUsize::new(0),
            block: unsafe { MaybeUninit::zeroed().assume_init() },
            next: AtomicPtr::default(),
        }
    }
}

struct AccessQueue {
    writing: AtomicPtr<AccessBlock>,
    full_list: AtomicPtr<AccessBlock>,
}

impl AccessBlock {
    fn new(item: CacheAccess) -> AccessBlock {
        let mut ret = AccessBlock {
            len: AtomicUsize::new(1),
            block: unsafe { MaybeUninit::zeroed().assume_init() },
            next: AtomicPtr::default(),
        };
        ret.block[0] = AtomicU64::from(u64::from(item));
        ret
    }
}

impl Default for AccessQueue {
    fn default() -> AccessQueue {
        AccessQueue {
            writing: AtomicPtr::new(Box::into_raw(Box::new(
                AccessBlock::default(),
            ))),
            full_list: AtomicPtr::default(),
        }
    }
}

impl AccessQueue {
    fn push(&self, item: CacheAccess) -> bool {
        loop {
            debug_delay();
            let head = self.writing.load(Ordering::Relaxed);
            let block = unsafe { &*head };

            debug_delay();
            let offset = block.len.fetch_add(1, Ordering::Acquire);

            if offset < MAX_QUEUE_ITEMS {
                let item_u64: u64 = item.into();
                assert_ne!(item_u64, 0);
                debug_delay();
                unsafe {
                    block
                        .block
                        .get_unchecked(offset)
                        .store(item_u64, Ordering::Release);
                }
                return false;
            } else {
                // install new writer
                let new = Box::into_raw(Box::new(AccessBlock::new(item)));
                debug_delay();
                let res = self.writing.compare_exchange_weak(
                    head,
                    new,
                    Ordering::Release,
                    Ordering::Relaxed,
                );
                if res.is_err() {
                    // we lost the CAS, free the new item that was
                    // never published to other threads
                    unsafe {
                        drop(Box::from_raw(new));
                    }
                    continue;
                }

                // push the now-full item to the full list for future
                // consumption
                let mut ret;
                let mut full_list_ptr = self.full_list.load(Ordering::Acquire);
                while {
                    // we loop because maybe other threads are pushing stuff too
                    block.next.store(full_list_ptr, Ordering::Release);
                    debug_delay();
                    ret = self.full_list.compare_exchange_weak(
                        full_list_ptr,
                        head,
                        Ordering::Release,
                        Ordering::Relaxed,
                    );
                    ret.is_err()
                } {
                    full_list_ptr = ret.unwrap_err();
                }
                return true;
            }
        }
    }

    fn take<'a>(&self, guard: &'a Guard) -> CacheAccessIter<'a> {
        debug_delay();
        let ptr = self.full_list.swap(std::ptr::null_mut(), Ordering::AcqRel);

        CacheAccessIter { guard, current_offset: 0, current_block: ptr }
    }
}

impl Drop for AccessQueue {
    fn drop(&mut self) {
        debug_delay();
        let writing = self.writing.load(Ordering::Acquire);
        unsafe {
            Box::from_raw(writing);
        }
        debug_delay();
        let mut head = self.full_list.load(Ordering::Acquire);
        while !head.is_null() {
            unsafe {
                debug_delay();
                let next =
                    (*head).next.swap(std::ptr::null_mut(), Ordering::Release);
                Box::from_raw(head);
                head = next;
            }
        }
    }
}

struct CacheAccessIter<'a> {
    guard: &'a Guard,
    current_offset: usize,
    current_block: *mut AccessBlock,
}

impl<'a> Iterator for CacheAccessIter<'a> {
    type Item = CacheAccess;

    fn next(&mut self) -> Option<CacheAccess> {
        while !self.current_block.is_null() {
            let current_block = unsafe { &*self.current_block };

            debug_delay();
            if self.current_offset >= MAX_QUEUE_ITEMS {
                let to_drop_ptr = self.current_block;
                debug_delay();
                self.current_block = current_block.next.load(Ordering::Acquire);
                self.current_offset = 0;
                debug_delay();
                let to_drop = unsafe { Box::from_raw(to_drop_ptr) };
                self.guard.defer(|| to_drop);
                continue;
            }

            let mut next = 0;
            while next == 0 {
                // we spin here because there's a race between bumping
                // the offset and setting the value to something other
                // than 0 (and 0 is an invalid value)
                debug_delay();
                next = current_block.block[self.current_offset]
                    .load(Ordering::Acquire);
            }
            self.current_offset += 1;
            return Some(CacheAccess::from(next));
        }

        None
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CacheAccess {
    // safe because MAX_PID_BITS / N_SHARDS < u32::MAX
    //                     2**37 /   2**8   < 2**32
    pub pid: u32,
    pub sz: u8,
}

impl From<CacheAccess> for u64 {
    fn from(ca: CacheAccess) -> u64 {
        (u64::from(ca.pid) << 8) | u64::from(ca.sz)
    }
}

#[allow(clippy::fallible_impl_from)]
impl From<u64> for CacheAccess {
    fn from(u: u64) -> CacheAccess {
        let sz = usize::try_from((u << 56) >> 56).unwrap();
        assert_ne!(sz, 0);
        let pid = u >> 8;
        assert!(pid < u64::from(std::u32::MAX));
        CacheAccess {
            pid: u32::try_from(pid).unwrap(),
            sz: u8::try_from(sz).unwrap(),
        }
    }
}

impl CacheAccess {
    fn size(&self) -> usize {
        1 << usize::from(self.sz)
    }

    fn new(pid: PageId, sz: usize) -> CacheAccess {
        let rounded_up_power_of_2 =
            u8::try_from(sz.next_power_of_two().trailing_zeros()).unwrap();

        CacheAccess {
            pid: u32::try_from(pid).expect("expected caller to shift pid down"),
            sz: rounded_up_power_of_2,
        }
    }
}

/// A simple LRU cache.
pub struct Lru {
    shards: Vec<(AccessQueue, FastLock<Shard>)>,
}

impl Lru {
    /// Instantiates a new `Lru` cache.
    pub(crate) fn new(cache_capacity: usize) -> Self {
        assert!(
            cache_capacity >= N_SHARDS,
            "Please configure the cache \
             capacity to be at least 256 bytes"
        );
        let shard_capacity = cache_capacity / N_SHARDS;

        let mut shards = Vec::with_capacity(N_SHARDS);
        shards.resize_with(N_SHARDS, || {
            (AccessQueue::default(), FastLock::new(Shard::new(shard_capacity)))
        });

        Self { shards }
    }

    /// Called when an item is accessed. Returns a Vec of items to be
    /// evicted. Uses flat-combining to avoid blocking on what can
    /// be an asynchronous operation.
    ///
    /// layout:
    ///   items:   1 2 3 4 5 6 7 8 9 10
    ///   shards:  1 0 1 0 1 0 1 0 1 0
    ///   shard 0:   2   4   6   8   10
    ///   shard 1: 1   3   5   7   9
    pub(crate) fn accessed(
        &self,
        id: PageId,
        item_size: usize,
        guard: &Guard,
    ) -> Vec<PageId> {
        const SHARD_BITS: usize = N_SHARDS.trailing_zeros() as usize;

        let mut ret = vec![];
        let shards = N_SHARDS as u64;
        let (shard_idx, shifted_pid) = (id % shards, id >> SHARD_BITS);
        let (access_queue, shard_mu) = &self.shards[safe_usize(shard_idx)];

        let cache_access = CacheAccess::new(shifted_pid, item_size);
        let filled = access_queue.push(cache_access);

        if filled {
            // only try to acquire this if the access queue has filled
            // an entire segment
            if let Some(mut shard) = shard_mu.try_lock() {
                let accesses = access_queue.take(guard);
                for item in accesses {
                    let to_evict = shard.accessed(item);
                    // map shard internal offsets to global items ids
                    for pos in to_evict {
                        let item =
                            (PageId::from(pos) << SHARD_BITS) + shard_idx;
                        ret.push(item);
                    }
                }
            }
        }
        ret
    }
}

#[derive(Eq)]
struct Entry(*mut Node);

unsafe impl Send for Entry {}

impl Ord for Entry {
    fn cmp(&self, other: &Entry) -> std::cmp::Ordering {
        let left_pid: u32 = *self.borrow();
        let right_pid: u32 = *other.borrow();
        left_pid.cmp(&right_pid)
    }
}

impl PartialOrd<Entry> for Entry {
    fn partial_cmp(&self, other: &Entry) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Entry {
    fn eq(&self, other: &Entry) -> bool {
        unsafe { (*self.0).pid == (*other.0).pid }
    }
}

impl BorrowMut<CacheAccess> for Entry {
    fn borrow_mut(&mut self) -> &mut CacheAccess {
        unsafe { &mut *self.0 }
    }
}

impl Borrow<CacheAccess> for Entry {
    fn borrow(&self) -> &CacheAccess {
        unsafe { &*self.0 }
    }
}

impl Borrow<u32> for Entry {
    fn borrow(&self) -> &u32 {
        unsafe { &(*self.0).pid }
    }
}

// we only hash on pid, since we will change
// sz sometimes and we access the item by pid
impl Hash for Entry {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        unsafe { (*self.0).pid.hash(hasher) }
    }
}

struct Shard {
    dll: DoublyLinkedList,
    entries: FastSet8<Entry>,
    capacity: usize,
    size: usize,
}

impl Shard {
    fn new(capacity: usize) -> Self {
        assert!(capacity > 0, "shard capacity must be non-zero");

        Self {
            dll: DoublyLinkedList::default(),
            entries: FastSet8::default(),
            capacity,
            size: 0,
        }
    }

    /// `PageId`s in the shard list are indexes of the entries.
    fn accessed(&mut self, cache_access: CacheAccess) -> Vec<u32> {
        if let Some(entry) = self.entries.get(&cache_access.pid) {
            let old_sz_po2 = unsafe { (*entry.0).swap_sz(cache_access.sz) };
            let old_size = 1 << usize::from(old_sz_po2);

            self.size -= old_size;
            self.dll.promote(entry.0);
        } else {
            let ptr = self.dll.push_head(cache_access);
            self.entries.insert(Entry(ptr));
        };

        self.size += cache_access.size();

        let mut to_evict = vec![];

        while self.size > self.capacity {
            if self.dll.len() == 1 {
                // don't evict what we just added
                break;
            }

            let min_pid = self.dll.pop_tail().unwrap();

            self.entries.remove(&min_pid.pid);

            to_evict.push(min_pid.pid);

            self.size -= min_pid.size();
        }

        to_evict
    }
}

#[inline]
fn safe_usize(value: PageId) -> usize {
    usize::try_from(value).unwrap()
}

#[test]
fn lru_smoke_test() {
    use crate::pin;

    let lru = Lru::new(2);
    for i in 0..1000 {
        let guard = pin();
        lru.accessed(i, 16, &guard);
    }
}

#[test]
fn lru_access_test() {
    use crate::pin;

    let ci = CacheAccess::new(6, 20667);
    assert_eq!(ci.size(), 32 * 1024);

    let lru = Lru::new(4096);

    let guard = pin();

    assert_eq!(lru.accessed(0, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(2, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(4, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(6, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(8, 20667, &guard), vec![0, 2, 4]);
    assert_eq!(lru.accessed(10, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(12, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(14, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(16, 20667, &guard), vec![6, 8, 10, 12]);
    assert_eq!(lru.accessed(18, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(20, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(22, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(24, 20667, &guard), vec![14, 16, 18, 20]);
}
<commit_msg>Revert Relaxed load to Acquire<commit_after>#![allow(unsafe_code)]

use std::{
    borrow::{Borrow, BorrowMut},
    convert::TryFrom,
    hash::{Hash, Hasher},
    mem::MaybeUninit,
    sync::atomic::{AtomicPtr, AtomicUsize, Ordering},
};

use crate::{
    atomic_shim::AtomicU64,
    debug_delay,
    dll::{DoublyLinkedList, Node},
    fastlock::FastLock,
    FastSet8, Guard, PageId,
};

#[cfg(any(test, feature = "lock_free_delays"))]
const MAX_QUEUE_ITEMS: usize = 4;

#[cfg(not(any(test, feature = "lock_free_delays")))]
const MAX_QUEUE_ITEMS: usize = 64;

#[cfg(any(test, feature = "lock_free_delays"))]
const N_SHARDS: usize = 2;

#[cfg(not(any(test, feature = "lock_free_delays")))]
const N_SHARDS: usize = 256;

struct AccessBlock {
    len: AtomicUsize,
    block: [AtomicU64; MAX_QUEUE_ITEMS],
    next: AtomicPtr<AccessBlock>,
}

impl Default for AccessBlock {
    fn default() -> AccessBlock {
        AccessBlock {
            len: AtomicUsize::new(0),
            block: unsafe { MaybeUninit::zeroed().assume_init() },
            next: AtomicPtr::default(),
        }
    }
}

struct AccessQueue {
    writing: AtomicPtr<AccessBlock>,
    full_list: AtomicPtr<AccessBlock>,
}

impl AccessBlock {
    fn new(item: CacheAccess) -> AccessBlock {
        let mut ret = AccessBlock {
            len: AtomicUsize::new(1),
            block: unsafe { MaybeUninit::zeroed().assume_init() },
            next: AtomicPtr::default(),
        };
        ret.block[0] = AtomicU64::from(u64::from(item));
        ret
    }
}

impl Default for AccessQueue {
    fn default() -> AccessQueue {
        AccessQueue {
            writing: AtomicPtr::new(Box::into_raw(Box::new(
                AccessBlock::default(),
            ))),
            full_list: AtomicPtr::default(),
        }
    }
}

impl AccessQueue {
    fn push(&self, item: CacheAccess) -> bool {
        loop {
            debug_delay();
            let head = self.writing.load(Ordering::Acquire);
            let block = unsafe { &*head };

            debug_delay();
            let offset = block.len.fetch_add(1, Ordering::Acquire);

            if offset < MAX_QUEUE_ITEMS {
                let item_u64: u64 = item.into();
                assert_ne!(item_u64, 0);
                debug_delay();
                unsafe {
                    block
                        .block
                        .get_unchecked(offset)
                        .store(item_u64, Ordering::Release);
                }
                return false;
            } else {
                // install new writer
                let new = Box::into_raw(Box::new(AccessBlock::new(item)));
                debug_delay();
                let res = self.writing.compare_exchange_weak(
                    head,
                    new,
                    Ordering::Release,
                    Ordering::Relaxed,
                );
                if res.is_err() {
                    // we lost the CAS, free the new item that was
                    // never published to other threads
                    unsafe {
                        drop(Box::from_raw(new));
                    }
                    continue;
                }

                // push the now-full item to the full list for future
                // consumption
                let mut ret;
                let mut full_list_ptr = self.full_list.load(Ordering::Acquire);
                while {
                    // we loop because maybe other threads are pushing stuff too
                    block.next.store(full_list_ptr, Ordering::Release);
                    debug_delay();
                    ret = self.full_list.compare_exchange_weak(
                        full_list_ptr,
                        head,
                        Ordering::Release,
                        Ordering::Relaxed,
                    );
                    ret.is_err()
                } {
                    full_list_ptr = ret.unwrap_err();
                }
                return true;
            }
        }
    }

    fn take<'a>(&self, guard: &'a Guard) -> CacheAccessIter<'a> {
        debug_delay();
        let ptr = self.full_list.swap(std::ptr::null_mut(), Ordering::AcqRel);

        CacheAccessIter { guard, current_offset: 0, current_block: ptr }
    }
}

impl Drop for AccessQueue {
    fn drop(&mut self) {
        debug_delay();
        let writing = self.writing.load(Ordering::Acquire);
        unsafe {
            Box::from_raw(writing);
        }
        debug_delay();
        let mut head = self.full_list.load(Ordering::Acquire);
        while !head.is_null() {
            unsafe {
                debug_delay();
                let next =
                    (*head).next.swap(std::ptr::null_mut(), Ordering::Release);
                Box::from_raw(head);
                head = next;
            }
        }
    }
}

struct CacheAccessIter<'a> {
    guard: &'a Guard,
    current_offset: usize,
    current_block: *mut AccessBlock,
}

impl<'a> Iterator for CacheAccessIter<'a> {
    type Item = CacheAccess;

    fn next(&mut self) -> Option<CacheAccess> {
        while !self.current_block.is_null() {
            let current_block = unsafe { &*self.current_block };

            debug_delay();
            if self.current_offset >= MAX_QUEUE_ITEMS {
                let to_drop_ptr = self.current_block;
                debug_delay();
                self.current_block = current_block.next.load(Ordering::Acquire);
                self.current_offset = 0;
                debug_delay();
                let to_drop = unsafe { Box::from_raw(to_drop_ptr) };
                self.guard.defer(|| to_drop);
                continue;
            }

            let mut next = 0;
            while next == 0 {
                // we spin here because there's a race between bumping
                // the offset and setting the value to something other
                // than 0 (and 0 is an invalid value)
                debug_delay();
                next = current_block.block[self.current_offset]
                    .load(Ordering::Acquire);
            }
            self.current_offset += 1;
            return Some(CacheAccess::from(next));
        }

        None
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CacheAccess {
    // safe because MAX_PID_BITS / N_SHARDS < u32::MAX
    //                     2**37 /   2**8   < 2**32
    pub pid: u32,
    pub sz: u8,
}

impl From<CacheAccess> for u64 {
    fn from(ca: CacheAccess) -> u64 {
        (u64::from(ca.pid) << 8) | u64::from(ca.sz)
    }
}

#[allow(clippy::fallible_impl_from)]
impl From<u64> for CacheAccess {
    fn from(u: u64) -> CacheAccess {
        let sz = usize::try_from((u << 56) >> 56).unwrap();
        assert_ne!(sz, 0);
        let pid = u >> 8;
        assert!(pid < u64::from(std::u32::MAX));
        CacheAccess {
            pid: u32::try_from(pid).unwrap(),
            sz: u8::try_from(sz).unwrap(),
        }
    }
}

impl CacheAccess {
    fn size(&self) -> usize {
        1 << usize::from(self.sz)
    }

    fn new(pid: PageId, sz: usize) -> CacheAccess {
        let rounded_up_power_of_2 =
            u8::try_from(sz.next_power_of_two().trailing_zeros()).unwrap();

        CacheAccess {
            pid: u32::try_from(pid).expect("expected caller to shift pid down"),
            sz: rounded_up_power_of_2,
        }
    }
}

/// A simple LRU cache.
pub struct Lru {
    shards: Vec<(AccessQueue, FastLock<Shard>)>,
}

impl Lru {
    /// Instantiates a new `Lru` cache.
    pub(crate) fn new(cache_capacity: usize) -> Self {
        assert!(
            cache_capacity >= N_SHARDS,
            "Please configure the cache \
             capacity to be at least 256 bytes"
        );
        let shard_capacity = cache_capacity / N_SHARDS;

        let mut shards = Vec::with_capacity(N_SHARDS);
        shards.resize_with(N_SHARDS, || {
            (AccessQueue::default(), FastLock::new(Shard::new(shard_capacity)))
        });

        Self { shards }
    }

    /// Called when an item is accessed. Returns a Vec of items to be
    /// evicted. Uses flat-combining to avoid blocking on what can
    /// be an asynchronous operation.
    ///
    /// layout:
    ///   items:   1 2 3 4 5 6 7 8 9 10
    ///   shards:  1 0 1 0 1 0 1 0 1 0
    ///   shard 0:   2   4   6   8   10
    ///   shard 1: 1   3   5   7   9
    pub(crate) fn accessed(
        &self,
        id: PageId,
        item_size: usize,
        guard: &Guard,
    ) -> Vec<PageId> {
        const SHARD_BITS: usize = N_SHARDS.trailing_zeros() as usize;

        let mut ret = vec![];
        let shards = N_SHARDS as u64;
        let (shard_idx, shifted_pid) = (id % shards, id >> SHARD_BITS);
        let (access_queue, shard_mu) = &self.shards[safe_usize(shard_idx)];

        let cache_access = CacheAccess::new(shifted_pid, item_size);
        let filled = access_queue.push(cache_access);

        if filled {
            // only try to acquire this if the access queue has filled
            // an entire segment
            if let Some(mut shard) = shard_mu.try_lock() {
                let accesses = access_queue.take(guard);
                for item in accesses {
                    let to_evict = shard.accessed(item);
                    // map shard internal offsets to global items ids
                    for pos in to_evict {
                        let item =
                            (PageId::from(pos) << SHARD_BITS) + shard_idx;
                        ret.push(item);
                    }
                }
            }
        }
        ret
    }
}

#[derive(Eq)]
struct Entry(*mut Node);

unsafe impl Send for Entry {}

impl Ord for Entry {
    fn cmp(&self, other: &Entry) -> std::cmp::Ordering {
        let left_pid: u32 = *self.borrow();
        let right_pid: u32 = *other.borrow();
        left_pid.cmp(&right_pid)
    }
}

impl PartialOrd<Entry> for Entry {
    fn partial_cmp(&self, other: &Entry) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Entry {
    fn eq(&self, other: &Entry) -> bool {
        unsafe { (*self.0).pid == (*other.0).pid }
    }
}

impl BorrowMut<CacheAccess> for Entry {
    fn borrow_mut(&mut self) -> &mut CacheAccess {
        unsafe { &mut *self.0 }
    }
}

impl Borrow<CacheAccess> for Entry {
    fn borrow(&self) -> &CacheAccess {
        unsafe { &*self.0 }
    }
}

impl Borrow<u32> for Entry {
    fn borrow(&self) -> &u32 {
        unsafe { &(*self.0).pid }
    }
}

// we only hash on pid, since we will change
// sz sometimes and we access the item by pid
impl Hash for Entry {
    fn hash<H: Hasher>(&self, hasher: &mut H) {
        unsafe { (*self.0).pid.hash(hasher) }
    }
}

struct Shard {
    dll: DoublyLinkedList,
    entries: FastSet8<Entry>,
    capacity: usize,
    size: usize,
}

impl Shard {
    fn new(capacity: usize) -> Self {
        assert!(capacity > 0, "shard capacity must be non-zero");

        Self {
            dll: DoublyLinkedList::default(),
            entries: FastSet8::default(),
            capacity,
            size: 0,
        }
    }

    /// `PageId`s in the shard list are indexes of the entries.
    fn accessed(&mut self, cache_access: CacheAccess) -> Vec<u32> {
        if let Some(entry) = self.entries.get(&cache_access.pid) {
            let old_sz_po2 = unsafe { (*entry.0).swap_sz(cache_access.sz) };
            let old_size = 1 << usize::from(old_sz_po2);

            self.size -= old_size;
            self.dll.promote(entry.0);
        } else {
            let ptr = self.dll.push_head(cache_access);
            self.entries.insert(Entry(ptr));
        };

        self.size += cache_access.size();

        let mut to_evict = vec![];

        while self.size > self.capacity {
            if self.dll.len() == 1 {
                // don't evict what we just added
                break;
            }

            let min_pid = self.dll.pop_tail().unwrap();

            self.entries.remove(&min_pid.pid);

            to_evict.push(min_pid.pid);

            self.size -= min_pid.size();
        }

        to_evict
    }
}

#[inline]
fn safe_usize(value: PageId) -> usize {
    usize::try_from(value).unwrap()
}

#[test]
fn lru_smoke_test() {
    use crate::pin;

    let lru = Lru::new(2);
    for i in 0..1000 {
        let guard = pin();
        lru.accessed(i, 16, &guard);
    }
}

#[test]
fn lru_access_test() {
    use crate::pin;

    let ci = CacheAccess::new(6, 20667);
    assert_eq!(ci.size(), 32 * 1024);

    let lru = Lru::new(4096);

    let guard = pin();

    assert_eq!(lru.accessed(0, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(2, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(4, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(6, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(8, 20667, &guard), vec![0, 2, 4]);
    assert_eq!(lru.accessed(10, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(12, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(14, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(16, 20667, &guard), vec![6, 8, 10, 12]);
    assert_eq!(lru.accessed(18, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(20, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(22, 20667, &guard), vec![]);
    assert_eq!(lru.accessed(24, 20667, &guard), vec![14, 16, 18, 20]);
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::env;
use std::fs::File;
use std::io::{ BufReader, BufWriter, Write };

extern crate env_logger;
extern crate tools;
use tools::config;

extern crate rustc_serialize;
use rustc_serialize::json;
use rustc_serialize::json::{ Json, ToJson };

fn read_json_from_file(path: &str) -> Option<json::Object> {
    let file = File::open(path).ok()?;
    let mut reader = BufReader::new(&file);
    Json::from_reader(&mut reader).ok()?.into_object()
}

fn write_json_to_file(val: Json, path: &str) -> Option<()> {
    let file = File::create(path).ok()?;
    let mut writer = BufWriter::new(file);
    write!(&mut writer, "{}", val).ok()?;
    Some(())
}

/// Helper to set the provided `key` to the provided `value` in the per-file for
/// the provided `path`, creating intermediary type="dir" nodes as we go.
///
/// This does not know how to set meta-info on directories at this time but can
/// be generalized in the future.
fn store_in_file_value(all_info: &mut json::Object, path: &str, key: &str, val: Json) {
    let mut dir_obj: &mut json::Object =
        all_info.get_mut("root").unwrap().as_object_mut().unwrap()
                .get_mut("contents").unwrap().as_object_mut().unwrap();
    // Walk the path segments, creating intermediary directory nodes
    // implicitly.
    let path_pieces: Vec<String> = path.split('/').map(|s| s.to_string()).collect();
    let (file_part, dir_parts) = path_pieces.split_last().unwrap();
    for path_component in dir_parts {
        let next_val = dir_obj.entry(path_component.to_string()).or_insert_with(|| {
            let mut child = BTreeMap::new();
            child.insert("type".to_string(), "dir".to_string().to_json());
            child.insert("contents".to_string(), Json::Object(json::Object::new()));
            Json::Object(child)
        });
        dir_obj = next_val.as_object_mut().unwrap()
                          .get_mut("contents").unwrap().as_object_mut().unwrap();
    }

    let file_val = dir_obj.entry(file_part.to_string()).or_insert_with(|| {
        let mut child = BTreeMap::new();
        child.insert("type".to_string(), "file".to_string().to_json());
        Json::Object(child)
    });
    let file_obj = file_val.as_object_mut().unwrap();
    file_obj.insert(key.to_string(), val);
}

/// Recursive helper to traverse the bugzilla component "paths" hierarchy and
/// propagate its values into the all_info structure.
///
/// - `bz_dir`: This will always be an object whose fields are filenames and
///   values will either be a recursively self-same directory object or a Number
///   which is a bugzilla components index.
/// - `all_node`: This will always be a { type: 'dir', contents } all_info
///   node.
fn traverse_and_store_bugzilla_map(bz_dir: &json::Object, all_node: &mut json::Object) {
    // We never actually want to be altering the metadata of the current
    // all_node, so just immediately access its contents.
    let all_contents = all_node.get_mut("contents").unwrap().as_object_mut().unwrap();
    for (filename, value) in bz_dir {
        if value.is_object() {
            // Objects mean the child is a directory as well.
            let all_child = all_contents.entry(filename.to_string()).or_insert_with(|| {
                let mut child = BTreeMap::new();
                child.insert("type".to_string(), "dir".to_string().to_json());
                child.insert("contents".to_string(), Json::Object(json::Object::new()));
                Json::Object(child)
            });
            traverse_and_store_bugzilla_map(
                value.as_object().unwrap(),
                all_child.as_object_mut().unwrap());
        } else {
            // It's a number and therefore a file.
            let all_child = all_contents.entry(filename.to_string()).or_insert_with(|| {
                let mut child = BTreeMap::new();
                child.insert("type".to_string(), "file".to_string().to_json());
                Json::Object(child)
            });
            let child_obj = all_child.as_object_mut().unwrap();
            child_obj.insert("component".to_string(), value.clone());
        }
    }
}

/// Process a number of JSON input files that provide per-file data into a
/// single aggregated JSON file.  The schema is taken from the bugzilla
/// `components-normalized.json` representation which is a compact/normalized
/// representation.
///
/// The intent of the resulting file is to provide a place for small amounts of
/// meta-information about a file, such as would be appropriate to display and
/// facet search results that match files or display in the directory listing.
///
/// Information that is verbose in nature and specific to the contents of a
/// file, such as code coverage information should instead be stored in separate
/// files-per-file.  This tool could still be responsible for performing the
/// aggregation of that information from disparate sources, however!
///
/// ## Input Files
///
/// ### bugzilla-components.json
///
/// - "components": A dictionary mapping from stringified numeric values to list
///   tuples of the form [product, component].
/// - "paths": A tree where internal nodes are dictionaries corresponding to
///   directories.  Each key is a filename and each value is either another
///   directory dictionary or a non-stringified number corresponding to an entry
///   in the `components` top-level dictionary.
///
/// ### test-info-all-tests.json
///
/// - "description": A string which conveys the date range and tree that this
///   data corresponds to.
/// - "summary": A dictionary with the following keys:
///   - "components"
///   - "failed tests"
///   - "manifests"
///   - "skipped tests"
///   - "tests"
/// - "tests": A dictionary whose keys are bugzilla "Product::Component" strings
///   and values are list of objects with the following keys:
///   - "failed runs": Number
///   - "skip-if" (optional): String excerpt of the manifest's skip-if clause.
///   - "skipped runs": Number
///   - "test": Repository-relative path of the test file.
///   - "total run time, seconds": Floating point number.
///   - "total runs": Number
///
/// ### WPT wpt-metadata-summary.json
///
/// Consult
/// https://searchfox.org/mozilla-central/source/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
/// for
///
/// - [directory]: A WPT-root (testing/web-platform/tests) string identifying a
///   directory containing tests.  Value is an object.
///   - "bug": Corresponds to a `bug: NNN` line in a meta-dir `__dir__.ini` file
///     with value payload `[null, "NNN"]`.
///   - "lsan-allowed": Corresponds to a `__dir__.ini`
///     `lsan-allowed: [Alloc, Create, ...]` line and results in `["Alloc",
///     "Create", ...]`.
///   - "_tests": An object whose keys are test file names.
///     - [test file name]: Value is an object which may contain any of the
///       following keys:
///       - "disabled": An array of 2-tuple arrays, where each 2-tuple is of the
///         form [if-predicate contents, bug URL].  So the line
///         `if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=NNN`
///         under a "disabled" mochitest ini-format header would result in
///         `["os == \"win\"\n", "https://bugzilla.mozilla.org/show_bug.cgi?id=NNN"]
///         and a line line the following directly under the test name
///         `disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=NNN` gives
///         `[null, "https://bugzilla.mozilla.org/show_bug.cgi?id=NNN"]`.
///         - It appears the bug URL's can just be straight bug numbers or
///           string bug aliases.
///       - "_subtests":
///         - [assertion string]: Payload is an object with optional keys:
///           - "intermittent": An array of nested tuples of the form
///             [condition clause, [ expected values ]].  For example, given
///             `if (processor == "x86") and debug: ["PASS", "FAIL"]` indented
///             beneath an `expected:` header results in
///             `["(processor == \"x86\") and debug\n", ["PASS", "FAIL"]]`.
///             - If this key is not present, then it appears this corresponds
///               to an ini entry of `expected: FAIL`, which would be equivalent
///               to `[null, ["FAIL"]]` I guess.
///       - "max-asserts": [condition?, max-asserts value]
///
/// ## Output File
///
/// For the time being we imitate the bugzilla-components.json representation.
/// A hierarchical tree representation is retained because the ability to
/// perform local lookups, walking up the tree as needed, is useful
/// functionality, as gecko is large enough that global coordination is
/// impractical, but component level coordination is feasible.
///
/// - "bugzilla-components": Directly from bugzilla-components.json, a
///   dictionary mapping from stringified numeric values to list tuples of the
///   form [product, component].
/// - "root": More explicit version of bugzilla-components.json's rep.  Each
///   node in the tree structure is an object with one of the following forms,
///   with the root having type "dir":
///   - { type: "dir", contents } where:
///     - "contents": An object directionary whose keys are filenames and value
///        nodes.
///   - { type: "file", component, testInfo } where:
///     - "component": Value is the numeric bugzilla component to be looked up.
///     - "testInfo": The value nodes from `test-info-alltests.json` verbatim.
///       In the future this may gain more data.
///     - "wptInfo": A digested version of the per-test info.  The presence of
///       this field means that a per-test metadata file exists which is itself
///       an indication of there being some kind of notable manipulation going
///       on even if we don't understand it specifically.
///       - "disabled": Directly propagated from the test-level "disabled".
fn main() {
    env_logger::init();

    let args: Vec<_> = env::args().collect();

    let cfg = config::load(&args[1], true);
    println!("Config file read");

    let tree_name = &args[2];
    let tree_config = cfg.trees.get(tree_name).unwrap();

    // ## Build empty derived info structure
    let mut all_info: json::Object = BTreeMap::new();
    {
        let contents = json::Object::new();
        let mut root = json::Object::new();
        root.insert("type".to_string(), "dir".to_string().to_json());
        root.insert("contents".to_string(), Json::Object(contents));
        all_info.insert("root".to_string(), Json::Object(root));
    }

    // ## Load bugzilla data and merge it in to the derived info structure
    let bugzilla_fname = format!("{}/bugzilla-components.json", tree_config.paths.index_path);
    let bugzilla_data = read_json_from_file(&bugzilla_fname);
    if let Some(mut data) = bugzilla_data {
        println!("Bugzilla components read");

        all_info.insert("bugzilla-components".to_string(),
                        data.remove("components").unwrap());

        if let Some(bz_root) = data.get("paths") {
            traverse_and_store_bugzilla_map(
                bz_root.as_object().unwrap(),
                all_info.get_mut("root").unwrap().as_object_mut().unwrap());
        }
    } else {
        println!("No bugzilla-components.json file found");
    }

    // ## Load test info and merge it in to the derived info structure
    let test_info_fname = format!("{}/test-info-all-tests.json", tree_config.paths.index_path);
    let test_info_data = read_json_from_file(&test_info_fname);
    if let Some(mut data) = test_info_data {
        println!("Test info data read");

        if let Some(Json::Object(tests_obj)) = data.remove("tests") {
            for (_, component_tests_value) in tests_obj.into_iter() {
                if let Json::Array(tests_arr) = component_tests_value {
                    for test_info_value in tests_arr.into_iter() {
                        let mut test_info_obj = match test_info_value {
                            Json::Object(obj) => obj,
                            _ => panic!("Test value should be an object.")
                        };
                        let test_path = match test_info_obj.remove("test") {
                            Some(Json::String(str)) => str,
                            _ => panic!("Test `test` field should be present and a string.")
                        };
                        store_in_file_value(
                            &mut all_info,
                            &test_path, "testInfo",
                            Json::Object(test_info_obj));
                    }
                }
            }
        }
    } else {
        println!("No test-info-all-tests.json file found");
    }

    // ## Load WPT meta info and merge it in to the derived info structure
    let wpt_info_fname = format!("{}/wpt-metadata-summary.json", tree_config.paths.index_path);
    let wpt_info_data = read_json_from_file(&wpt_info_fname);
    if let (Some(wpt_root), Some(data)) = (tree_config.paths.wpt_root.clone(), wpt_info_data) {
        println!("WPT info read");

        for (dir_path, dir_info) in data.into_iter() {
            // Process only the tests info.  There may be some notable stuff
            // here at the directory's `__dir__.ini` level, but we're not doing
            // anything with it yet.
            if let Some(Json::Object(tests_obj)) = dir_info.into_object().unwrap().remove("_tests") {
                for (test_filename, test_info) in tests_obj.into_iter() {
                    let mut propagate = BTreeMap::new();
                    // Process "disabled" which indicates there were file-level
                    // failure disablings.

                    let mut test_obj = test_info.into_object().unwrap();
                    if let Some(conditions) = test_obj.remove("disabled") {
                        propagate.insert("disabled".to_string(), conditions);
                    }

                    if let Some(Json::Object(subtests_obj)) = test_obj.remove("_subtests") {
                        propagate.insert(
                            "subtests_with_conditions".to_string(),
                            Json::U64(subtests_obj.len() as u64));
                    }

                    store_in_file_value(
                        &mut all_info,
                        &format!("{}/tests/{}/{}", wpt_root, dir_path, test_filename),
                        "wptInfo",
                        Json::Object(propagate));
                }
            }
        }
    } else {
        println!("No wpt-metadata-summary.json file found");
    }

    // ## Write out the derived info structure
    let output_fname = format!("{}/derived-per-file-info.json", tree_config.paths.index_path);
     if write_json_to_file(Json::Object(all_info), &output_fname).is_some() {
         println!("Per-file info written to disk");
     } else {
         println!("Unable to write per-file info to disk");
     }
}
<commit_msg>Bug 1653986 - change printlns to appropriate info/warn. r=kats<commit_after>use std::collections::BTreeMap;
use std::env;
use std::fs::File;
use std::io::{ BufReader, BufWriter, Write };

extern crate env_logger;
#[macro_use]
extern crate log;
extern crate tools;
use tools::config;

extern crate rustc_serialize;
use rustc_serialize::json;
use rustc_serialize::json::{ Json, ToJson };

fn read_json_from_file(path: &str) -> Option<json::Object> {
    let file = File::open(path).ok()?;
    let mut reader = BufReader::new(&file);
    Json::from_reader(&mut reader).ok()?.into_object()
}

fn write_json_to_file(val: Json, path: &str) -> Option<()> {
    let file = File::create(path).ok()?;
    let mut writer = BufWriter::new(file);
    write!(&mut writer, "{}", val).ok()?;
    Some(())
}

/// Helper to set the provided `key` to the provided `value` in the per-file for
/// the provided `path`, creating intermediary type="dir" nodes as we go.
///
/// This does not know how to set meta-info on directories at this time but can
/// be generalized in the future.
fn store_in_file_value(all_info: &mut json::Object, path: &str, key: &str, val: Json) {
    let mut dir_obj: &mut json::Object =
        all_info.get_mut("root").unwrap().as_object_mut().unwrap()
                .get_mut("contents").unwrap().as_object_mut().unwrap();
    // Walk the path segments, creating intermediary directory nodes
    // implicitly.
    let path_pieces: Vec<String> = path.split('/').map(|s| s.to_string()).collect();
    let (file_part, dir_parts) = path_pieces.split_last().unwrap();
    for path_component in dir_parts {
        let next_val = dir_obj.entry(path_component.to_string()).or_insert_with(|| {
            let mut child = BTreeMap::new();
            child.insert("type".to_string(), "dir".to_string().to_json());
            child.insert("contents".to_string(), Json::Object(json::Object::new()));
            Json::Object(child)
        });
        dir_obj = next_val.as_object_mut().unwrap()
                          .get_mut("contents").unwrap().as_object_mut().unwrap();
    }

    let file_val = dir_obj.entry(file_part.to_string()).or_insert_with(|| {
        let mut child = BTreeMap::new();
        child.insert("type".to_string(), "file".to_string().to_json());
        Json::Object(child)
    });
    let file_obj = file_val.as_object_mut().unwrap();
    file_obj.insert(key.to_string(), val);
}

/// Recursive helper to traverse the bugzilla component "paths" hierarchy and
/// propagate its values into the all_info structure.
///
/// - `bz_dir`: This will always be an object whose fields are filenames and
///   values will either be a recursively self-same directory object or a Number
///   which is a bugzilla components index.
/// - `all_node`: This will always be a { type: 'dir', contents } all_info
///   node.
fn traverse_and_store_bugzilla_map(bz_dir: &json::Object, all_node: &mut json::Object) {
    // We never actually want to be altering the metadata of the current
    // all_node, so just immediately access its contents.
    let all_contents = all_node.get_mut("contents").unwrap().as_object_mut().unwrap();
    for (filename, value) in bz_dir {
        if value.is_object() {
            // Objects mean the child is a directory as well.
            let all_child = all_contents.entry(filename.to_string()).or_insert_with(|| {
                let mut child = BTreeMap::new();
                child.insert("type".to_string(), "dir".to_string().to_json());
                child.insert("contents".to_string(), Json::Object(json::Object::new()));
                Json::Object(child)
            });
            traverse_and_store_bugzilla_map(
                value.as_object().unwrap(),
                all_child.as_object_mut().unwrap());
        } else {
            // It's a number and therefore a file.
            let all_child = all_contents.entry(filename.to_string()).or_insert_with(|| {
                let mut child = BTreeMap::new();
                child.insert("type".to_string(), "file".to_string().to_json());
                Json::Object(child)
            });
            let child_obj = all_child.as_object_mut().unwrap();
            child_obj.insert("component".to_string(), value.clone());
        }
    }
}

/// Process a number of JSON input files that provide per-file data into a
/// single aggregated JSON file.  The schema is taken from the bugzilla
/// `components-normalized.json` representation which is a compact/normalized
/// representation.
///
/// The intent of the resulting file is to provide a place for small amounts of
/// meta-information about a file, such as would be appropriate to display and
/// facet search results that match files or display in the directory listing.
///
/// Information that is verbose in nature and specific to the contents of a
/// file, such as code coverage information should instead be stored in separate
/// files-per-file.  This tool could still be responsible for performing the
/// aggregation of that information from disparate sources, however!
///
/// ## Input Files
///
/// ### bugzilla-components.json
///
/// - "components": A dictionary mapping from stringified numeric values to list
///   tuples of the form [product, component].
/// - "paths": A tree where internal nodes are dictionaries corresponding to
///   directories.  Each key is a filename and each value is either another
///   directory dictionary or a non-stringified number corresponding to an entry
///   in the `components` top-level dictionary.
///
/// ### test-info-all-tests.json
///
/// - "description": A string which conveys the date range and tree that this
///   data corresponds to.
/// - "summary": A dictionary with the following keys:
///   - "components"
///   - "failed tests"
///   - "manifests"
///   - "skipped tests"
///   - "tests"
/// - "tests": A dictionary whose keys are bugzilla "Product::Component" strings
///   and values are list of objects with the following keys:
///   - "failed runs": Number
///   - "skip-if" (optional): String excerpt of the manifest's skip-if clause.
///   - "skipped runs": Number
///   - "test": Repository-relative path of the test file.
///   - "total run time, seconds": Floating point number.
///   - "total runs": Number
///
/// ### WPT wpt-metadata-summary.json
///
/// Consult
/// https://searchfox.org/mozilla-central/source/testing/web-platform/tests/tools/wptrunner/wptrunner/manifestexpected.py
/// for
///
/// - [directory]: A WPT-root (testing/web-platform/tests) string identifying a
///   directory containing tests.  Value is an object.
///   - "bug": Corresponds to a `bug: NNN` line in a meta-dir `__dir__.ini` file
///     with value payload `[null, "NNN"]`.
///   - "lsan-allowed": Corresponds to a `__dir__.ini`
///     `lsan-allowed: [Alloc, Create, ...]` line and results in `["Alloc",
///     "Create", ...]`.
///   - "_tests": An object whose keys are test file names.
///     - [test file name]: Value is an object which may contain any of the
///       following keys:
///       - "disabled": An array of 2-tuple arrays, where each 2-tuple is of the
///         form [if-predicate contents, bug URL].  So the line
///         `if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=NNN`
///         under a "disabled" mochitest ini-format header would result in
///         `["os == \"win\"\n", "https://bugzilla.mozilla.org/show_bug.cgi?id=NNN"]
///         and a line line the following directly under the test name
///         `disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=NNN` gives
///         `[null, "https://bugzilla.mozilla.org/show_bug.cgi?id=NNN"]`.
///         - It appears the bug URL's can just be straight bug numbers or
///           string bug aliases.
///       - "_subtests":
///         - [assertion string]: Payload is an object with optional keys:
///           - "intermittent": An array of nested tuples of the form
///             [condition clause, [ expected values ]].  For example, given
///             `if (processor == "x86") and debug: ["PASS", "FAIL"]` indented
///             beneath an `expected:` header results in
///             `["(processor == \"x86\") and debug\n", ["PASS", "FAIL"]]`.
///             - If this key is not present, then it appears this corresponds
///               to an ini entry of `expected: FAIL`, which would be equivalent
///               to `[null, ["FAIL"]]` I guess.
///       - "max-asserts": [condition?, max-asserts value]
///
/// ## Output File
///
/// For the time being we imitate the bugzilla-components.json representation.
/// A hierarchical tree representation is retained because the ability to
/// perform local lookups, walking up the tree as needed, is useful
/// functionality, as gecko is large enough that global coordination is
/// impractical, but component level coordination is feasible.
///
/// - "bugzilla-components": Directly from bugzilla-components.json, a
///   dictionary mapping from stringified numeric values to list tuples of the
///   form [product, component].
/// - "root": More explicit version of bugzilla-components.json's rep.  Each
///   node in the tree structure is an object with one of the following forms,
///   with the root having type "dir":
///   - { type: "dir", contents } where:
///     - "contents": An object directionary whose keys are filenames and value
///        nodes.
///   - { type: "file", component, testInfo } where:
///     - "component": Value is the numeric bugzilla component to be looked up.
///     - "testInfo": The value nodes from `test-info-alltests.json` verbatim.
///       In the future this may gain more data.
///     - "wptInfo": A digested version of the per-test info.  The presence of
///       this field means that a per-test metadata file exists which is itself
///       an indication of there being some kind of notable manipulation going
///       on even if we don't understand it specifically.
///       - "disabled": Directly propagated from the test-level "disabled".
fn main() {
    env_logger::init();

    let args: Vec<_> = env::args().collect();

    let cfg = config::load(&args[1], true);
    println!("Config file read");

    let tree_name = &args[2];
    let tree_config = cfg.trees.get(tree_name).unwrap();

    // ## Build empty derived info structure
    let mut all_info: json::Object = BTreeMap::new();
    {
        let contents = json::Object::new();
        let mut root = json::Object::new();
        root.insert("type".to_string(), "dir".to_string().to_json());
        root.insert("contents".to_string(), Json::Object(contents));
        all_info.insert("root".to_string(), Json::Object(root));
    }

    // ## Load bugzilla data and merge it in to the derived info structure
    let bugzilla_fname = format!("{}/bugzilla-components.json", tree_config.paths.index_path);
    let bugzilla_data = read_json_from_file(&bugzilla_fname);
    if let Some(mut data) = bugzilla_data {
        info!("Bugzilla components read");

        all_info.insert("bugzilla-components".to_string(),
                        data.remove("components").unwrap());

        if let Some(bz_root) = data.get("paths") {
            traverse_and_store_bugzilla_map(
                bz_root.as_object().unwrap(),
                all_info.get_mut("root").unwrap().as_object_mut().unwrap());
        }
    } else {
        warn!("No bugzilla-components.json file found");
    }

    // ## Load test info and merge it in to the derived info structure
    let test_info_fname = format!("{}/test-info-all-tests.json", tree_config.paths.index_path);
    let test_info_data = read_json_from_file(&test_info_fname);
    if let Some(mut data) = test_info_data {
        info!("Test info data read");

        if let Some(Json::Object(tests_obj)) = data.remove("tests") {
            for (_, component_tests_value) in tests_obj.into_iter() {
                if let Json::Array(tests_arr) = component_tests_value {
                    for test_info_value in tests_arr.into_iter() {
                        let mut test_info_obj = match test_info_value {
                            Json::Object(obj) => obj,
                            _ => panic!("Test value should be an object.")
                        };
                        let test_path = match test_info_obj.remove("test") {
                            Some(Json::String(str)) => str,
                            _ => panic!("Test `test` field should be present and a string.")
                        };
                        store_in_file_value(
                            &mut all_info,
                            &test_path, "testInfo",
                            Json::Object(test_info_obj));
                    }
                }
            }
        }
    } else {
        warn!("No test-info-all-tests.json file found");
    }

    // ## Load WPT meta info and merge it in to the derived info structure
    let wpt_info_fname = format!("{}/wpt-metadata-summary.json", tree_config.paths.index_path);
    let wpt_info_data = read_json_from_file(&wpt_info_fname);
    if let (Some(wpt_root), Some(data)) = (tree_config.paths.wpt_root.clone(), wpt_info_data) {
        info!("WPT info read");

        for (dir_path, dir_info) in data.into_iter() {
            // Process only the tests info.  There may be some notable stuff
            // here at the directory's `__dir__.ini` level, but we're not doing
            // anything with it yet.
            if let Some(Json::Object(tests_obj)) = dir_info.into_object().unwrap().remove("_tests") {
                for (test_filename, test_info) in tests_obj.into_iter() {
                    let mut propagate = BTreeMap::new();
                    // Process "disabled" which indicates there were file-level
                    // failure disablings.

                    let mut test_obj = test_info.into_object().unwrap();
                    if let Some(conditions) = test_obj.remove("disabled") {
                        propagate.insert("disabled".to_string(), conditions);
                    }

                    if let Some(Json::Object(subtests_obj)) = test_obj.remove("_subtests") {
                        propagate.insert(
                            "subtests_with_conditions".to_string(),
                            Json::U64(subtests_obj.len() as u64));
                    }

                    store_in_file_value(
                        &mut all_info,
                        &format!("{}/tests/{}/{}", wpt_root, dir_path, test_filename),
                        "wptInfo",
                        Json::Object(propagate));
                }
            }
        }
    } else {
        warn!("No wpt-metadata-summary.json file found");
    }

    // ## Write out the derived info structure
    let output_fname = format!("{}/derived-per-file-info.json", tree_config.paths.index_path);
     if write_json_to_file(Json::Object(all_info), &output_fname).is_some() {
        info!("Per-file info written to disk");
     } else {
        warn!("Unable to write per-file info to disk");
     }
}
<|endoftext|>


<commit_before>use super::ir::*;

use crate::internal_signal;
use crate::state_elements::*;

use std::collections::HashMap;

pub(super) struct Compiler<'graph> {
    signal_exprs: HashMap<&'graph internal_signal::InternalSignal<'graph>, Expr>,
}

impl<'graph, 'context> Compiler<'graph> {
    pub fn new() -> Compiler<'graph> {
        Compiler {
            signal_exprs: HashMap::new(),
        }
    }

    pub fn compile_signal(
        &mut self,
        signal: &'graph internal_signal::InternalSignal<'graph>,
        state_elements: &'context StateElements<'graph>,
        a: &mut AssignmentContext,
    ) -> Expr {
        enum Frame<'graph> {
            Enter(&'graph internal_signal::InternalSignal<'graph>),
            Leave(&'graph internal_signal::InternalSignal<'graph>),
        }

        let mut frames = Vec::new();
        frames.push(Frame::Enter(signal));

        let mut results = Vec::new();

        while let Some(frame) = frames.pop() {
            if let Some(expr) = match frame {
                Frame::Enter(signal) => {
                    if let Some(expr) = self.signal_exprs.get(&signal) {
                        results.push(expr.clone());
                        continue;
                    }

                    match signal.data {
                        internal_signal::SignalData::Lit {
                            ref value,
                            bit_width,
                        } => Some(Expr::from_constant(value, bit_width)),

                        internal_signal::SignalData::Input { data } => {
                            if let Some(driven_value) = data.driven_value.borrow().clone() {
                                frames.push(Frame::Enter(driven_value));
                                None
                            } else {
                                Some(Expr::Ref { name: data.name.clone() })
                            }
                        }
                        internal_signal::SignalData::Output { data } => {
                            frames.push(Frame::Enter(data.source));
                            None
                        }

                        internal_signal::SignalData::Reg { .. } => Some(Expr::Ref {
                            name: state_elements.regs[&signal].value_name.clone(),
                        }),

                        internal_signal::SignalData::UnOp { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }
                        internal_signal::SignalData::SimpleBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::AdditiveBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::ComparisonBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::ShiftBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Mul { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::MulSigned { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Bits { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }

                        internal_signal::SignalData::Repeat { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }
                        internal_signal::SignalData::Concat { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Mux {
                            cond,
                            when_true,
                            when_false,
                            ..
                        } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(cond));
                            frames.push(Frame::Enter(when_true));
                            frames.push(Frame::Enter(when_false));
                            None
                        }

                        internal_signal::SignalData::MemReadPortOutput {
                            mem,
                            address,
                            enable,
                        } => {
                            let mem = &state_elements.mems[&mem];
                            let read_signal_names = &mem.read_signal_names[&(address, enable)];
                            Some(Expr::Ref {
                                name: read_signal_names.value_name.clone(),
                            })
                        }
                    }
                }
                Frame::Leave(signal) => {
                    match signal.data {
                        internal_signal::SignalData::Lit { .. } => unreachable!(),

                        internal_signal::SignalData::Input { .. } => unreachable!(),
                        internal_signal::SignalData::Output { .. } => unreachable!(),

                        internal_signal::SignalData::Reg { .. } => unreachable!(),

                        internal_signal::SignalData::UnOp { op, bit_width, .. } => {
                            let source = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::UnOp {
                                    source: Box::new(source),
                                    op: match op {
                                        internal_signal::UnOp::Not => UnOp::Not,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::SimpleBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::SimpleBinOp::BitAnd => BinOp::BitAnd,
                                        internal_signal::SimpleBinOp::BitOr => BinOp::BitOr,
                                        internal_signal::SimpleBinOp::BitXor => BinOp::BitXor,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::AdditiveBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::AdditiveBinOp::Add => BinOp::Add,
                                        internal_signal::AdditiveBinOp::Sub => BinOp::Sub,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::ComparisonBinOp { op, .. } => {
                            let bit_width = signal.bit_width();
                            let mut lhs = results.pop().unwrap();
                            let mut rhs = results.pop().unwrap();
                            match op {
                                internal_signal::ComparisonBinOp::GreaterThanEqualSigned
                                | internal_signal::ComparisonBinOp::GreaterThanSigned
                                | internal_signal::ComparisonBinOp::LessThanEqualSigned
                                | internal_signal::ComparisonBinOp::LessThanSigned => {
                                    lhs = Expr::Signed {
                                        source: Box::new(lhs),
                                    };
                                    rhs = Expr::Signed {
                                        source: Box::new(rhs),
                                    };
                                }
                                _ => (),
                            }
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::ComparisonBinOp::Equal => BinOp::Equal,
                                        internal_signal::ComparisonBinOp::NotEqual => BinOp::NotEqual,
                                        internal_signal::ComparisonBinOp::LessThan
                                        | internal_signal::ComparisonBinOp::LessThanSigned => BinOp::LessThan,
                                        internal_signal::ComparisonBinOp::LessThanEqual
                                        | internal_signal::ComparisonBinOp::LessThanEqualSigned => {
                                            BinOp::LessThanEqual
                                        }
                                        internal_signal::ComparisonBinOp::GreaterThan
                                        | internal_signal::ComparisonBinOp::GreaterThanSigned => {
                                            BinOp::GreaterThan
                                        }
                                        internal_signal::ComparisonBinOp::GreaterThanEqual
                                        | internal_signal::ComparisonBinOp::GreaterThanEqualSigned => {
                                            BinOp::GreaterThanEqual
                                        }
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::ShiftBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::ShiftBinOp::Shl => BinOp::Shl,
                                        internal_signal::ShiftBinOp::Shr => BinOp::Shr,
                                        internal_signal::ShiftBinOp::ShrArithmetic => BinOp::ShrArithmetic,
                                    },
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Mul { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: BinOp::Mul,
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::MulSigned { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            let lhs = Expr::Signed {
                                source: Box::new(lhs),
                            };
                            let rhs = Expr::Signed {
                                source: Box::new(rhs),
                            };
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: BinOp::Mul,
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Bits {
                            source,
                            range_high,
                            range_low,
                        } => {
                            let source_bit_width = source.bit_width();
                            let bit_width = signal.bit_width();
                            let source = results.pop().unwrap();
                            // Verilog doesn't allow indexing scalars
                            Some(if source_bit_width == 1 {
                                source
                            } else {
                                a.gen_temp(
                                    Expr::Bits {
                                        source: Box::new(source),
                                        range_high,
                                        range_low,
                                    },
                                    bit_width,
                                )
                            })
                        }

                        internal_signal::SignalData::Repeat {
                            count, bit_width, ..
                        } => {
                            let source = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Repeat {
                                    source: Box::new(source),
                                    count,
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::Concat { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Concat {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Mux { bit_width, .. } => {
                            let cond = results.pop().unwrap();
                            let when_true = results.pop().unwrap();
                            let when_false = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Ternary {
                                    cond: Box::new(cond),
                                    when_true: Box::new(when_true),
                                    when_false: Box::new(when_false),
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::MemReadPortOutput { .. } => unreachable!(),
                    }
                }
            } {
                self.signal_exprs.insert(signal, expr.clone());
                results.push(expr);
            }
        }

        results.pop().unwrap()
    }
}
<commit_msg>Fix verilog gen for arithmetic right shift<commit_after>use super::ir::*;

use crate::internal_signal;
use crate::state_elements::*;

use std::collections::HashMap;

pub(super) struct Compiler<'graph> {
    signal_exprs: HashMap<&'graph internal_signal::InternalSignal<'graph>, Expr>,
}

impl<'graph, 'context> Compiler<'graph> {
    pub fn new() -> Compiler<'graph> {
        Compiler {
            signal_exprs: HashMap::new(),
        }
    }

    pub fn compile_signal(
        &mut self,
        signal: &'graph internal_signal::InternalSignal<'graph>,
        state_elements: &'context StateElements<'graph>,
        a: &mut AssignmentContext,
    ) -> Expr {
        enum Frame<'graph> {
            Enter(&'graph internal_signal::InternalSignal<'graph>),
            Leave(&'graph internal_signal::InternalSignal<'graph>),
        }

        let mut frames = Vec::new();
        frames.push(Frame::Enter(signal));

        let mut results = Vec::new();

        while let Some(frame) = frames.pop() {
            if let Some(expr) = match frame {
                Frame::Enter(signal) => {
                    if let Some(expr) = self.signal_exprs.get(&signal) {
                        results.push(expr.clone());
                        continue;
                    }

                    match signal.data {
                        internal_signal::SignalData::Lit {
                            ref value,
                            bit_width,
                        } => Some(Expr::from_constant(value, bit_width)),

                        internal_signal::SignalData::Input { data } => {
                            if let Some(driven_value) = data.driven_value.borrow().clone() {
                                frames.push(Frame::Enter(driven_value));
                                None
                            } else {
                                Some(Expr::Ref { name: data.name.clone() })
                            }
                        }
                        internal_signal::SignalData::Output { data } => {
                            frames.push(Frame::Enter(data.source));
                            None
                        }

                        internal_signal::SignalData::Reg { .. } => Some(Expr::Ref {
                            name: state_elements.regs[&signal].value_name.clone(),
                        }),

                        internal_signal::SignalData::UnOp { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }
                        internal_signal::SignalData::SimpleBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::AdditiveBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::ComparisonBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::ShiftBinOp { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Mul { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }
                        internal_signal::SignalData::MulSigned { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Bits { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }

                        internal_signal::SignalData::Repeat { source, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(source));
                            None
                        }
                        internal_signal::SignalData::Concat { lhs, rhs, .. } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(lhs));
                            frames.push(Frame::Enter(rhs));
                            None
                        }

                        internal_signal::SignalData::Mux {
                            cond,
                            when_true,
                            when_false,
                            ..
                        } => {
                            frames.push(Frame::Leave(signal));
                            frames.push(Frame::Enter(cond));
                            frames.push(Frame::Enter(when_true));
                            frames.push(Frame::Enter(when_false));
                            None
                        }

                        internal_signal::SignalData::MemReadPortOutput {
                            mem,
                            address,
                            enable,
                        } => {
                            let mem = &state_elements.mems[&mem];
                            let read_signal_names = &mem.read_signal_names[&(address, enable)];
                            Some(Expr::Ref {
                                name: read_signal_names.value_name.clone(),
                            })
                        }
                    }
                }
                Frame::Leave(signal) => {
                    match signal.data {
                        internal_signal::SignalData::Lit { .. } => unreachable!(),

                        internal_signal::SignalData::Input { .. } => unreachable!(),
                        internal_signal::SignalData::Output { .. } => unreachable!(),

                        internal_signal::SignalData::Reg { .. } => unreachable!(),

                        internal_signal::SignalData::UnOp { op, bit_width, .. } => {
                            let source = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::UnOp {
                                    source: Box::new(source),
                                    op: match op {
                                        internal_signal::UnOp::Not => UnOp::Not,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::SimpleBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::SimpleBinOp::BitAnd => BinOp::BitAnd,
                                        internal_signal::SimpleBinOp::BitOr => BinOp::BitOr,
                                        internal_signal::SimpleBinOp::BitXor => BinOp::BitXor,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::AdditiveBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::AdditiveBinOp::Add => BinOp::Add,
                                        internal_signal::AdditiveBinOp::Sub => BinOp::Sub,
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::ComparisonBinOp { op, .. } => {
                            let bit_width = signal.bit_width();
                            let mut lhs = results.pop().unwrap();
                            let mut rhs = results.pop().unwrap();
                            match op {
                                internal_signal::ComparisonBinOp::GreaterThanEqualSigned
                                | internal_signal::ComparisonBinOp::GreaterThanSigned
                                | internal_signal::ComparisonBinOp::LessThanEqualSigned
                                | internal_signal::ComparisonBinOp::LessThanSigned => {
                                    lhs = Expr::Signed {
                                        source: Box::new(lhs),
                                    };
                                    rhs = Expr::Signed {
                                        source: Box::new(rhs),
                                    };
                                }
                                _ => (),
                            }
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::ComparisonBinOp::Equal => BinOp::Equal,
                                        internal_signal::ComparisonBinOp::NotEqual => BinOp::NotEqual,
                                        internal_signal::ComparisonBinOp::LessThan
                                        | internal_signal::ComparisonBinOp::LessThanSigned => BinOp::LessThan,
                                        internal_signal::ComparisonBinOp::LessThanEqual
                                        | internal_signal::ComparisonBinOp::LessThanEqualSigned => {
                                            BinOp::LessThanEqual
                                        }
                                        internal_signal::ComparisonBinOp::GreaterThan
                                        | internal_signal::ComparisonBinOp::GreaterThanSigned => {
                                            BinOp::GreaterThan
                                        }
                                        internal_signal::ComparisonBinOp::GreaterThanEqual
                                        | internal_signal::ComparisonBinOp::GreaterThanEqualSigned => {
                                            BinOp::GreaterThanEqual
                                        }
                                    },
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::ShiftBinOp { op, bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let lhs = match op {
                                internal_signal::ShiftBinOp::Shl | internal_signal::ShiftBinOp::Shr => lhs,
                                internal_signal::ShiftBinOp::ShrArithmetic => Expr::Signed {
                                    source: Box::new(lhs),
                                },
                            };
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: match op {
                                        internal_signal::ShiftBinOp::Shl => BinOp::Shl,
                                        internal_signal::ShiftBinOp::Shr => BinOp::Shr,
                                        internal_signal::ShiftBinOp::ShrArithmetic => BinOp::ShrArithmetic,
                                    },
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Mul { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: BinOp::Mul,
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::MulSigned { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            let lhs = Expr::Signed {
                                source: Box::new(lhs),
                            };
                            let rhs = Expr::Signed {
                                source: Box::new(rhs),
                            };
                            Some(a.gen_temp(
                                Expr::BinOp {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                    op: BinOp::Mul,
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Bits {
                            source,
                            range_high,
                            range_low,
                        } => {
                            let source_bit_width = source.bit_width();
                            let bit_width = signal.bit_width();
                            let source = results.pop().unwrap();
                            // Verilog doesn't allow indexing scalars
                            Some(if source_bit_width == 1 {
                                source
                            } else {
                                a.gen_temp(
                                    Expr::Bits {
                                        source: Box::new(source),
                                        range_high,
                                        range_low,
                                    },
                                    bit_width,
                                )
                            })
                        }

                        internal_signal::SignalData::Repeat {
                            count, bit_width, ..
                        } => {
                            let source = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Repeat {
                                    source: Box::new(source),
                                    count,
                                },
                                bit_width,
                            ))
                        }
                        internal_signal::SignalData::Concat { bit_width, .. } => {
                            let lhs = results.pop().unwrap();
                            let rhs = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Concat {
                                    lhs: Box::new(lhs),
                                    rhs: Box::new(rhs),
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::Mux { bit_width, .. } => {
                            let cond = results.pop().unwrap();
                            let when_true = results.pop().unwrap();
                            let when_false = results.pop().unwrap();
                            Some(a.gen_temp(
                                Expr::Ternary {
                                    cond: Box::new(cond),
                                    when_true: Box::new(when_true),
                                    when_false: Box::new(when_false),
                                },
                                bit_width,
                            ))
                        }

                        internal_signal::SignalData::MemReadPortOutput { .. } => unreachable!(),
                    }
                }
            } {
                self.signal_exprs.insert(signal, expr.clone());
                results.push(expr);
            }
        }

        results.pop().unwrap()
    }
}
<|endoftext|>