]> git.lizzy.rs Git - rust.git/blob - src/tools/unicode-table-generator/src/raw_emitter.rs
Add a right shift mapping
[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             ShiftRight(u32),
237         }
238
239         // key is the word being mapped to
240         let mut mappings: BTreeMap<u64, Vec<(u64, Mapping)>> = BTreeMap::new();
241         for &a in unique_words {
242             'b: for &b in unique_words {
243                 // skip self
244                 if a == b {
245                     continue;
246                 }
247
248                 // All possible distinct rotations
249                 for rotation in 1..64 {
250                     if a.rotate_right(rotation) == b {
251                         mappings.entry(b).or_default().push((a, Mapping::Rotate(rotation)));
252                         // We're not interested in further mappings between a and b
253                         continue 'b;
254                     }
255                 }
256
257                 if (!a) == b {
258                     mappings.entry(b).or_default().push((a, Mapping::Invert));
259                     // We're not interested in further mappings between a and b
260                     continue 'b;
261                 }
262
263                 // All possible distinct rotations, inverted
264                 for rotation in 1..64 {
265                     if (!a.rotate_right(rotation)) == b {
266                         mappings
267                             .entry(b)
268                             .or_default()
269                             .push((a, Mapping::RotateAndInvert(rotation)));
270                         // We're not interested in further mappings between a and b
271                         continue 'b;
272                     }
273                 }
274
275                 // All possible shifts
276                 for shift_by in 1..64 {
277                     if a == (b >> shift_by) {
278                         mappings
279                             .entry(b)
280                             .or_default()
281                             .push((a, Mapping::ShiftRight(shift_by as u32)));
282                         // We're not interested in further mappings between a and b
283                         continue 'b;
284                     }
285                 }
286             }
287         }
288         // These are the bitset words which will be represented "raw" (as a u64)
289         let mut canonical_words = Vec::new();
290         // These are mapped words, which will be represented by an index into
291         // the canonical_words and a Mapping; u16 when encoded.
292         let mut canonicalized_words = Vec::new();
293         let mut unique_mapping = HashMap::new();
294
295         #[derive(Debug, PartialEq, Eq)]
296         enum UniqueMapping {
297             Canonical(usize),
298             Canonicalized(usize),
299         }
300
301         while let Some((&to, _)) = mappings.iter().max_by_key(|m| m.1.len()) {
302             // Get the mapping with the most entries. Currently, no mapping can
303             // only exist transitively (i.e., there is no A, B, C such that A
304             // does not map to C and but A maps to B maps to C), so this is
305             // guaranteed to be acceptable.
306             //
307             // In the future, we may need a more sophisticated algorithm to
308             // identify which keys to prefer as canonical.
309             let mapped_from = mappings.remove(&to).unwrap();
310             for (from, how) in &mapped_from {
311                 // Remove the entries which mapped to this one.
312                 // Noting that it should be associated with the Nth canonical word.
313                 //
314                 // We do not assert that this is present, because there may be
315                 // no mappings to the `from` word; that's fine.
316                 mappings.remove(from);
317                 assert_eq!(
318                     unique_mapping
319                         .insert(*from, UniqueMapping::Canonicalized(canonicalized_words.len())),
320                     None
321                 );
322                 canonicalized_words.push((canonical_words.len(), *how));
323
324                 // Remove the now-canonicalized word from other mappings,
325                 // to ensure that we deprioritize them in the next iteration of
326                 // the while loop.
327                 for (_, mapped) in &mut mappings {
328                     let mut i = 0;
329                     while i != mapped.len() {
330                         if mapped[i].0 == *from {
331                             mapped.remove(i);
332                         } else {
333                             i += 1;
334                         }
335                     }
336                 }
337             }
338             assert!(
339                 unique_mapping
340                     .insert(to, UniqueMapping::Canonical(canonical_words.len()))
341                     .is_none()
342             );
343             canonical_words.push(to);
344
345             // Remove the now-canonical word from other mappings, to ensure that
346             // we deprioritize them in the next iteration of the while loop.
347             for (_, mapped) in &mut mappings {
348                 let mut i = 0;
349                 while i != mapped.len() {
350                     if mapped[i].0 == to {
351                         mapped.remove(i);
352                     } else {
353                         i += 1;
354                     }
355                 }
356             }
357         }
358
359         // Any words which we couldn't shrink, just stick into the canonical
360         // words.
361         //
362         // FIXME: work harder -- there are more possibilities for mapping
363         // functions (e.g., multiplication, shifting instead of rotation, etc.)
364         // We'll probably always have some slack though so this loop will still
365         // be needed.
366         for &w in unique_words {
367             if !unique_mapping.contains_key(&w) {
368                 assert!(
369                     unique_mapping
370                         .insert(w, UniqueMapping::Canonical(canonical_words.len()))
371                         .is_none()
372                 );
373                 canonical_words.push(w);
374             }
375         }
376         assert_eq!(canonicalized_words.len() + canonical_words.len(), unique_words.len());
377         assert_eq!(unique_mapping.len(), unique_words.len());
378
379         let unique_mapping = unique_mapping
380             .into_iter()
381             .map(|(key, value)| {
382                 (
383                     key,
384                     match value {
385                         UniqueMapping::Canonicalized(idx) => {
386                             u8::try_from(canonical_words.len() + idx).unwrap()
387                         }
388                         UniqueMapping::Canonical(idx) => u8::try_from(idx).unwrap(),
389                     },
390                 )
391             })
392             .collect::<HashMap<_, _>>();
393
394         let mut distinct_indices = BTreeSet::new();
395         for &w in unique_words {
396             let idx = unique_mapping.get(&w).unwrap();
397             assert!(distinct_indices.insert(idx));
398         }
399
400         const LOWER_6: u32 = (1 << 6) - 1;
401
402         let canonicalized_words = canonicalized_words
403             .into_iter()
404             .map(|v| {
405                 (
406                     u8::try_from(v.0).unwrap(),
407                     match v.1 {
408                         Mapping::RotateAndInvert(amount) => {
409                             assert_eq!(amount, amount & LOWER_6);
410                             1 << 6 | (amount as u8)
411                         }
412                         Mapping::Rotate(amount) => {
413                             assert_eq!(amount, amount & LOWER_6);
414                             amount as u8
415                         }
416                         Mapping::Invert => 1 << 6,
417                         Mapping::ShiftRight(shift_by) => {
418                             assert_eq!(shift_by, shift_by & LOWER_6);
419                             1 << 7 | (shift_by as u8)
420                         }
421                     },
422                 )
423             })
424             .collect::<Vec<(u8, u8)>>();
425         Canonicalized { unique_mapping, canonical_words, canonicalized_words }
426     }
427 }