1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Jump table representation.
//!
//! Jump tables are declared in the preamble and assigned an `ir::entities::JumpTable` reference.
//! The actual table of destinations is stored in a `JumpTableData` struct defined in this module.

use crate::ir::instructions::ValueListPool;
use crate::ir::BlockCall;
use alloc::vec::Vec;
use core::fmt::{self, Display, Formatter};
use core::slice::{Iter, IterMut};

#[cfg(feature = "enable-serde")]
use serde_derive::{Deserialize, Serialize};

/// Contents of a jump table.
///
/// All jump tables use 0-based indexing and are densely populated.
///
/// The default block for the jump table is stored as the first element of the underlying vector.
/// It can be accessed through the `default_block` and `default_block_mut` functions. All blocks
/// may be iterated using the `all_branches` and `all_branches_mut` functions, which will both
/// iterate over the default block first.
#[derive(Clone, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct JumpTableData {
    // Table entries.
    table: Vec<BlockCall>,
}

impl JumpTableData {
    /// Create a new jump table with the provided blocks.
    pub fn new(def: BlockCall, table: &[BlockCall]) -> Self {
        Self {
            table: std::iter::once(def).chain(table.iter().copied()).collect(),
        }
    }

    /// Fetch the default block for this jump table.
    pub fn default_block(&self) -> BlockCall {
        *self.table.first().unwrap()
    }

    /// Mutable access to the default block of this jump table.
    pub fn default_block_mut(&mut self) -> &mut BlockCall {
        self.table.first_mut().unwrap()
    }

    /// The jump table and default block as a single slice. The default block will always be first.
    pub fn all_branches(&self) -> &[BlockCall] {
        self.table.as_slice()
    }

    /// The jump table and default block as a single mutable slice. The default block will always
    /// be first.
    pub fn all_branches_mut(&mut self) -> &mut [BlockCall] {
        self.table.as_mut_slice()
    }

    /// Access the jump table as a slice. This excludes the default block.
    pub fn as_slice(&self) -> &[BlockCall] {
        &self.table.as_slice()[1..]
    }

    /// Access the jump table as a mutable slice. This excludes the default block.
    pub fn as_mut_slice(&mut self) -> &mut [BlockCall] {
        &mut self.table.as_mut_slice()[1..]
    }

    /// Returns an iterator to the jump table, excluding the default block.
    #[deprecated(since = "7.0.0", note = "please use `.as_slice()` instead")]
    pub fn iter(&self) -> Iter<BlockCall> {
        self.as_slice().iter()
    }

    /// Returns an iterator that allows modifying each value, excluding the default block.
    #[deprecated(since = "7.0.0", note = "please use `.as_mut_slice()` instead")]
    pub fn iter_mut(&mut self) -> IterMut<BlockCall> {
        self.as_mut_slice().iter_mut()
    }

    /// Clears all entries in this jump table, except for the default block.
    pub fn clear(&mut self) {
        self.table.drain(1..);
    }

    /// Return a value that can display the contents of this jump table.
    pub fn display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayJumpTable<'a> {
        DisplayJumpTable { jt: self, pool }
    }
}

/// A wrapper for the context required to display a [JumpTableData].
pub struct DisplayJumpTable<'a> {
    jt: &'a JumpTableData,
    pool: &'a ValueListPool,
}

impl<'a> Display for DisplayJumpTable<'a> {
    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
        write!(fmt, "{}, [", self.jt.default_block().display(self.pool))?;
        if let Some((first, rest)) = self.jt.as_slice().split_first() {
            write!(fmt, "{}", first.display(self.pool))?;
            for block in rest {
                write!(fmt, ", {}", block.display(self.pool))?;
            }
        }
        write!(fmt, "]")
    }
}

#[cfg(test)]
mod tests {
    use super::JumpTableData;
    use crate::entity::EntityRef;
    use crate::ir::instructions::ValueListPool;
    use crate::ir::{Block, BlockCall, Value};
    use std::string::ToString;

    #[test]
    fn empty() {
        let mut pool = ValueListPool::default();
        let def = BlockCall::new(Block::new(0), &[], &mut pool);

        let jt = JumpTableData::new(def, &[]);

        assert_eq!(jt.all_branches().get(0), Some(&def));

        assert_eq!(jt.as_slice().get(0), None);
        assert_eq!(jt.as_slice().get(10), None);

        assert_eq!(jt.display(&pool).to_string(), "block0, []");

        assert_eq!(jt.all_branches(), [def]);
        assert_eq!(jt.as_slice(), []);
    }

    #[test]
    fn insert() {
        let mut pool = ValueListPool::default();

        let v0 = Value::new(0);
        let v1 = Value::new(1);

        let e0 = Block::new(0);
        let e1 = Block::new(1);
        let e2 = Block::new(2);

        let def = BlockCall::new(e0, &[], &mut pool);
        let b1 = BlockCall::new(e1, &[v0], &mut pool);
        let b2 = BlockCall::new(e2, &[], &mut pool);
        let b3 = BlockCall::new(e1, &[v1], &mut pool);

        let jt = JumpTableData::new(def, &[b1, b2, b3]);

        assert_eq!(jt.default_block(), def);
        assert_eq!(
            jt.display(&pool).to_string(),
            "block0, [block1(v0), block2, block1(v1)]"
        );

        assert_eq!(jt.all_branches(), [def, b1, b2, b3]);
        assert_eq!(jt.as_slice(), [b1, b2, b3]);

        assert_eq!(jt.as_slice()[0].args_slice(&pool), [v0]);
        assert_eq!(jt.as_slice()[1].args_slice(&pool), []);
        assert_eq!(jt.as_slice()[2].args_slice(&pool), [v1]);
    }
}