]> git.lizzy.rs Git - rust.git/blob - src/tools/unicode-table-generator/src/raw_emitter.rs
38b36c34042287168a6936faf9ae918d5c7fd390
[rust.git] / src / tools / unicode-table-generator / src / raw_emitter.rs
1 //! This implements the core logic of the compression scheme used to compactly
2 //! encode the Unicode character classes.
3 //!
4 //! The primary idea is that we 'flatten' the Unicode ranges into an enormous
5 //! bitset. To represent any arbitrary codepoint in a raw bitset, we would need
6 //! over 17 kilobytes of data per character set -- way too much for our
7 //! purposes.
8 //!
9 //! We have two primary goals with the encoding: we want to be compact, because
10 //! these tables often end up in ~every Rust program (especially the
11 //! grapheme_extend table, used for str debugging), including those for embedded
12 //! targets (where space is important). We also want to be relatively fast,
13 //! though this is more of a nice to have rather than a key design constraint.
14 //! In practice, due to modern processor design these two are closely related.
15 //!
16 //! The encoding scheme here compresses the bitset by first deduplicating the
17 //! "words" (64 bits on all platforms). In practice very few words are present
18 //! in most data sets.
19 //!
20 //! This gives us an array that maps `u8 -> word` (if we ever went beyond 256
21 //! words, we could go to u16 -> word or have some dual compression scheme
22 //! mapping into two separate sets; currently this is not dealt with).
23 //!
24 //! With that scheme, we now have a single byte for every 64 codepoints. We
25 //! further group these by some constant N (between 1 and 64 per group), and
26 //! again deduplicate and store in an array (u8 -> [u8; N]). The constant is
27 //! chosen to be optimal in bytes-in-memory for the given dataset.
28 //!
29 //! The indices into this array represent ranges of 64*16 = 1024 codepoints.
30 //!
31 //! This already reduces the top-level array to at most 1,086 bytes, but in
32 //! practice we usually can encode in far fewer (the first couple Unicode planes
33 //! are dense).
34 //!
35 //! The last byte of this top-level array is pulled out to a separate static
36 //! and trailing zeros are dropped; this is simply because grapheme_extend and
37 //! case_ignorable have a single entry in the 896th entry, so this shrinks them
38 //! down considerably.
39
40 use crate::fmt_list;
41 use std::collections::{BTreeMap, BTreeSet, HashMap};
42 use std::convert::TryFrom;
43 use std::fmt::{self, Write};
44 use std::ops::Range;
45
46 #[derive(Clone)]
47 pub struct RawEmitter {
48     pub file: String,
49     pub bytes_used: usize,
50 }
51
52 impl RawEmitter {
53     pub fn new() -> RawEmitter {
54         RawEmitter { file: String::new(), bytes_used: 0 }
55     }
56
57     fn blank_line(&mut self) {
58         if self.file.is_empty() || self.file.ends_with("\n\n") {
59             return;
60         }
61         writeln!(&mut self.file, "").unwrap();
62     }
63
64     fn emit_bitset(&mut self, words: &[u64]) {
65         let mut words = words.to_vec();
66         // Ensure that there's a zero word in the dataset, used for padding and
67         // such.
68         words.push(0);
69         let unique_words =
70             words.iter().cloned().collect::<BTreeSet<_>>().into_iter().collect::<Vec<_>>();
71         if unique_words.len() > u8::max_value() as usize {
72             panic!("cannot pack {} into 8 bits", unique_words.len());
73         }
74         // needed for the chunk mapping to work
75         assert_eq!(unique_words[0], 0, "has a zero word");
76         let canonicalized = Canonicalized::canonicalize(&unique_words);
77
78         let word_indices = canonicalized.unique_mapping.clone();
79         let compressed_words = words.iter().map(|w| word_indices[w]).collect::<Vec<u8>>();
80
81         let mut best = None;
82         for length in 1..=64 {
83             let mut temp = self.clone();
84             temp.emit_chunk_map(word_indices[&0], &compressed_words, length);
85             if let Some((_, size)) = best {
86                 if temp.bytes_used < size {
87                     best = Some((length, temp.bytes_used));
88                 }
89             } else {
90                 best = Some((length, temp.bytes_used));
91             }
92         }
93         self.emit_chunk_map(word_indices[&0], &compressed_words, best.unwrap().0);
94
95         struct Bits(u64);
96         impl fmt::Debug for Bits {
97             fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98                 write!(f, "0b{:064b}", self.0)
99             }
100         }
101
102         writeln!(
103             &mut self.file,
104             "static BITSET_CANONICAL: [u64; {}] = [{}];",
105             canonicalized.canonical_words.len(),
106             fmt_list(canonicalized.canonical_words.iter().map(|v| Bits(*v))),
107         )
108         .unwrap();
109         self.bytes_used += 8 * canonicalized.canonical_words.len();
110         writeln!(
111             &mut self.file,
112             "static BITSET_MAPPING: [(u8, u8); {}] = [{}];",
113             canonicalized.canonicalized_words.len(),
114             fmt_list(&canonicalized.canonicalized_words),
115         )
116         .unwrap();
117         // 8 bit index into shifted words, 7 bits for shift + optional flip
118         // We only need it for the words that we removed by applying a shift and
119         // flip to them.
120         self.bytes_used += 2 * canonicalized.canonicalized_words.len();
121     }
122
123     fn emit_chunk_map(&mut self, zero_at: u8, compressed_words: &[u8], chunk_length: usize) {
124         let mut compressed_words = compressed_words.to_vec();
125         for _ in 0..(chunk_length - (compressed_words.len() % chunk_length)) {
126             // pad out bitset index with zero words so we have all chunks of
127             // chunkchunk_length
128             compressed_words.push(zero_at);
129         }
130
131         let mut chunks = BTreeSet::new();
132         for chunk in compressed_words.chunks(chunk_length) {
133             chunks.insert(chunk);
134         }
135         let chunk_map = chunks
136             .clone()
137             .into_iter()
138             .enumerate()
139             .map(|(idx, chunk)| (chunk, idx))
140             .collect::<HashMap<_, _>>();
141         let mut chunk_indices = Vec::new();
142         for chunk in compressed_words.chunks(chunk_length) {
143             chunk_indices.push(chunk_map[chunk]);
144         }
145
146         // If one of the chunks has all of the entries point to the bitset
147         // word filled with zeros, then pop those off the end -- we know they
148         // are useless.
149         let zero_chunk_idx = chunks.iter().position(|chunk| chunk.iter().all(|e| *e == zero_at));
150         while zero_chunk_idx.is_some() && chunk_indices.last().cloned() == zero_chunk_idx {
151             chunk_indices.pop();
152         }
153         writeln!(
154             &mut self.file,
155             "static BITSET_LAST_CHUNK_MAP: (u16, u8) = ({}, {});",
156             chunk_indices.len() - 1,
157             chunk_indices.pop().unwrap(),
158         )
159         .unwrap();
160         self.bytes_used += 3;
161         // Try to pop again, now that we've recorded a non-zero pointing index
162         // into the LAST_CHUNK_MAP.
163         while zero_chunk_idx.is_some() && chunk_indices.last().cloned() == zero_chunk_idx {
164             chunk_indices.pop();
165         }
166         writeln!(
167             &mut self.file,
168             "static BITSET_CHUNKS_MAP: [u8; {}] = [{}];",
169             chunk_indices.len(),
170             fmt_list(&chunk_indices),
171         )
172         .unwrap();
173         self.bytes_used += chunk_indices.len();
174         writeln!(
175             &mut self.file,
176             "static BITSET_INDEX_CHUNKS: [[u8; {}]; {}] = [{}];",
177             chunk_length,
178             chunks.len(),
179             fmt_list(chunks.iter()),
180         )
181         .unwrap();
182         self.bytes_used += chunk_length * chunks.len();
183     }
184
185     pub fn emit_lookup(&mut self) {
186         writeln!(&mut self.file, "pub fn lookup(c: char) -> bool {{").unwrap();
187         writeln!(&mut self.file, "    super::range_search(",).unwrap();
188         writeln!(&mut self.file, "        c as u32,").unwrap();
189         writeln!(&mut self.file, "        &BITSET_CHUNKS_MAP,").unwrap();
190         writeln!(&mut self.file, "        BITSET_LAST_CHUNK_MAP,").unwrap();
191         writeln!(&mut self.file, "        &BITSET_INDEX_CHUNKS,").unwrap();
192         writeln!(&mut self.file, "        &BITSET_CANONICAL,").unwrap();
193         writeln!(&mut self.file, "        &BITSET_MAPPING,").unwrap();
194         writeln!(&mut self.file, "    )").unwrap();
195         writeln!(&mut self.file, "}}").unwrap();
196     }
197 }
198
199 pub fn emit_codepoints(emitter: &mut RawEmitter, ranges: &[Range<u32>]) {
200     emitter.blank_line();
201
202     let last_code_point = ranges.last().unwrap().end;
203     // bitset for every bit in the codepoint range
204     //
205     // + 2 to ensure an all zero word to use for padding
206     let mut buckets = vec![0u64; (last_code_point as usize / 64) + 2];
207     for range in ranges {
208         for codepoint in range.clone() {
209             let bucket = codepoint as usize / 64;
210             let bit = codepoint as u64 % 64;
211             buckets[bucket] |= 1 << bit;
212         }
213     }
214
215     emitter.emit_bitset(&buckets);
216     emitter.blank_line();
217     emitter.emit_lookup();
218 }
219
220 struct Canonicalized {
221     canonical_words: Vec<u64>,
222     canonicalized_words: Vec<(u8, u8)>,
223
224     /// Maps an input unique word to the associated index (u8) which is into
225     /// canonical_words or canonicalized_words (in order).
226     unique_mapping: HashMap<u64, u8>,
227 }
228
229 impl Canonicalized {
230     fn canonicalize(unique_words: &[u64]) -> Self {
231         #[derive(Copy, Clone, Debug)]
232         enum Mapping {
233             Rotate(u32),
234             Invert,
235             RotateAndInvert(u32),
236         }
237
238         // key is the word being mapped to
239         let mut mappings: BTreeMap<u64, Vec<(u64, Mapping)>> = BTreeMap::new();
240         for &a in unique_words {
241             'b: for &b in unique_words {
242                 // skip self
243                 if a == b {
244                     continue;
245                 }
246
247                 // All possible distinct rotations
248                 for rotation in 1..64 {
249                     if a.rotate_right(rotation) == b {
250                         mappings.entry(b).or_default().push((a, Mapping::Rotate(rotation)));
251                         // We're not interested in further mappings between a and b
252                         continue 'b;
253                     }
254                 }
255
256                 if (!a) == b {
257                     mappings.entry(b).or_default().push((a, Mapping::Invert));
258                     // We're not interested in further mappings between a and b
259                     continue 'b;
260                 }
261
262                 // All possible distinct rotations, inverted
263                 for rotation in 1..64 {
264                     if (!a.rotate_right(rotation)) == b {
265                         mappings
266                             .entry(b)
267                             .or_default()
268                             .push((a, Mapping::RotateAndInvert(rotation)));
269                         // We're not interested in further mappings between a and b
270                         continue 'b;
271                     }
272                 }
273             }
274         }
275         // These are the bitset words which will be represented "raw" (as a u64)
276         let mut canonical_words = Vec::new();
277         // These are mapped words, which will be represented by an index into
278         // the canonical_words and a Mapping; u16 when encoded.
279         let mut canonicalized_words = Vec::new();
280         let mut unique_mapping = HashMap::new();
281
282         #[derive(Debug, PartialEq, Eq)]
283         enum UniqueMapping {
284             Canonical(usize),
285             Canonicalized(usize),
286         }
287
288         while let Some((&to, _)) = mappings.iter().max_by_key(|m| m.1.len()) {
289             // Get the mapping with the most entries. Currently, no mapping can
290             // only exist transitively (i.e., there is no A, B, C such that A
291             // does not map to C and but A maps to B maps to C), so this is
292             // guaranteed to be acceptable.
293             //
294             // In the future, we may need a more sophisticated algorithm to
295             // identify which keys to prefer as canonical.
296             let mapped_from = mappings.remove(&to).unwrap();
297             for (from, how) in &mapped_from {
298                 // Remove the entries which mapped to this one.
299                 // Noting that it should be associated with the Nth canonical word.
300                 //
301                 // We do not assert that this is present, because there may be
302                 // no mappings to the `from` word; that's fine.
303                 mappings.remove(from);
304                 assert_eq!(
305                     unique_mapping
306                         .insert(*from, UniqueMapping::Canonicalized(canonicalized_words.len())),
307                     None
308                 );
309                 canonicalized_words.push((canonical_words.len(), *how));
310
311                 // Remove the now-canonicalized word from other mappings,
312                 // to ensure that we deprioritize them in the next iteration of
313                 // the while loop.
314                 for (_, mapped) in &mut mappings {
315                     let mut i = 0;
316                     while i != mapped.len() {
317                         if mapped[i].0 == *from {
318                             mapped.remove(i);
319                         } else {
320                             i += 1;
321                         }
322                     }
323                 }
324             }
325             assert!(
326                 unique_mapping
327                     .insert(to, UniqueMapping::Canonical(canonical_words.len()))
328                     .is_none()
329             );
330             canonical_words.push(to);
331
332             // Remove the now-canonical word from other mappings, to ensure that
333             // we deprioritize them in the next iteration of the while loop.
334             for (_, mapped) in &mut mappings {
335                 let mut i = 0;
336                 while i != mapped.len() {
337                     if mapped[i].0 == to {
338                         mapped.remove(i);
339                     } else {
340                         i += 1;
341                     }
342                 }
343             }
344         }
345
346         // Any words which we couldn't shrink, just stick into the canonical
347         // words.
348         //
349         // FIXME: work harder -- there are more possibilities for mapping
350         // functions (e.g., multiplication, shifting instead of rotation, etc.)
351         // We'll probably always have some slack though so this loop will still
352         // be needed.
353         for &w in unique_words {
354             if !unique_mapping.contains_key(&w) {
355                 assert!(
356                     unique_mapping
357                         .insert(w, UniqueMapping::Canonical(canonical_words.len()))
358                         .is_none()
359                 );
360                 canonical_words.push(w);
361             }
362         }
363         assert_eq!(canonicalized_words.len() + canonical_words.len(), unique_words.len());
364         assert_eq!(unique_mapping.len(), unique_words.len());
365
366         let unique_mapping = unique_mapping
367             .into_iter()
368             .map(|(key, value)| {
369                 (
370                     key,
371                     match value {
372                         UniqueMapping::Canonicalized(idx) => {
373                             u8::try_from(canonical_words.len() + idx).unwrap()
374                         }
375                         UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(),
376                     },
377                 )
378             })
379             .collect::<HashMap<_, _>>();
380
381         let mut distinct_indices = BTreeSet::new();
382         for &w in unique_words {
383             let idx = unique_mapping.get(&w).unwrap();
384             assert!(distinct_indices.insert(idx));
385         }
386
387         let canonicalized_words = canonicalized_words
388             .into_iter()
389             .map(|v| {
390                 (
391                     u8::try_from(v.0).unwrap(),
392                     match v.1 {
393                         Mapping::RotateAndInvert(amount) => {
394                             assert!(amount < (1 << 7));
395                             1 << 7 | (amount as u8)
396                         }
397                         Mapping::Rotate(amount) => {
398                             assert!(amount < (1 << 7));
399                             amount as u8
400                         }
401                         Mapping::Invert => 1 << 7,
402                     },
403                 )
404             })
405             .collect::<Vec<(u8, u8)>>();
406         Canonicalized { unique_mapping, canonical_words, canonicalized_words }
407     }
408 }